From c3c6780de7111b1a9bc2ddbae02360cb418af647 Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Mon, 5 Feb 2024 17:45:26 -0500 Subject: [PATCH 001/623] Remving Python from build --- README.md | 40 +++++++++++++++++++++++++++++++++++++++- ci/build_python.sh | 21 --------------------- ci/build_wheel_cuvs.sh | 4 ++-- ci/test_python.sh | 26 +++++++++++++------------- ci/test_wheel_cuvs.sh | 20 ++++++++++---------- docs/source/index.rst | 10 +++------- 6 files changed, 67 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index e86eafda2e..428ab04a95 100755 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ -#
 cuVS: Vector Search on the GPU
+#
 cuVS: Vector Search and Clustering on the GPU
+ +### NOTE: cuVS is currently being + +## Contents +
+ +1. [Useful Resources](#useful-resources) +2. [What is cuVS?](#what-is-cuvs) +3. [Getting Started](#getting-started) +4. [Installing cuVS](#installing) +5. [Contributing](#contributing) +6. [References](#references) + +
+ +## Useful Resources + +- [cuVS Reference Documentation](https://docs.rapids.ai/api/cuvs/stable/): API Documentation. +- [cuVS Getting Started](./docs/source/quick_start.md): Getting started with RAFT. +- [Build and Install cuVS](./docs/source/build.md): Instructions for installing and building cuVS. +- [Example Notebooks](./notebooks): Example jupyer notebooks +- [RAPIDS Community](https://rapids.ai/community.html): Get help, contribute, and collaborate. +- [GitHub repository](https://github.com/rapidsai/cuvs): Download the cuVS source code. +- [Issue tracker](https://github.com/rapidsai/cuvs/issues): Report issues or request features. + +## What is cuVS? + +cuVS contains many algorithms for running approximate nearest neighbors and clustering on the GPU. + +## Getting Started + + + +## Installing cuVS + +## Contributing + +## References diff --git a/ci/build_python.sh b/ci/build_python.sh index a54c3dcaa9..525d21c513 100755 --- a/ci/build_python.sh +++ b/ci/build_python.sh @@ -34,24 +34,3 @@ rapids-conda-retry mambabuild \ --no-test \ --channel "${CPP_CHANNEL}" \ conda/recipes/cuvs - - -# Build ann-bench for each cuda and python version -rapids-conda-retry mambabuild \ ---no-test \ ---channel "${CPP_CHANNEL}" \ ---channel "${RAPIDS_CONDA_BLD_OUTPUT_DIR}" \ -conda/recipes/cuda-ann-bench - -# Build ann-bench-cpu only in CUDA 11 jobs since it only depends on python -# version -RAPIDS_CUDA_MAJOR="${RAPIDS_CUDA_VERSION%%.*}" -if [[ ${RAPIDS_CUDA_MAJOR} == "11" ]]; then - rapids-conda-retry mambabuild \ - --no-test \ - --channel "${CPP_CHANNEL}" \ - --channel "${RAPIDS_CONDA_BLD_OUTPUT_DIR}" \ - conda/recipes/cuda-ann-bench-cpu -fi - -rapids-upload-conda-to-s3 python diff --git a/ci/build_wheel_cuvs.sh b/ci/build_wheel_cuvs.sh index 9d2f969964..b4765be38a 100755 --- a/ci/build_wheel_cuvs.sh +++ b/ci/build_wheel_cuvs.sh @@ -1,9 +1,9 @@ #!/bin/bash -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. set -euo pipefail # Set up skbuild options. Enable sccache in skbuild config options export SKBUILD_CONFIGURE_OPTIONS="-DRAFT_BUILD_WHEELS=ON -DDETECT_CONDA_ENV=OFF -DFIND_RAFT_CPP=OFF" -ci/build_wheel.sh cuvs python/cuvs +#ci/build_wheel.sh cuvs python/cuvs diff --git a/ci/test_python.sh b/ci/test_python.sh index a65469928c..9f0c9d6ee7 100755 --- a/ci/test_python.sh +++ b/ci/test_python.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. set -euo pipefail @@ -31,7 +31,7 @@ rapids-print-env rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ - libcuvs cuvs + libcuvs #cuvs rapids-logger "Check GPU usage" nvidia-smi @@ -40,17 +40,17 @@ EXITCODE=0 trap "EXITCODE=1" ERR set +e -rapids-logger "pytest cuvs" -pushd python/cuvs/cuvs -pytest \ - --cache-clear \ - --junitxml="${RAPIDS_TESTS_DIR}/junit-cuvs.xml" \ - --cov-config=../.coveragerc \ - --cov=cuvs \ - --cov-report=xml:"${RAPIDS_COVERAGE_DIR}/cuvs-coverage.xml" \ - --cov-report=term \ - test -popd +#rapids-logger "pytest cuvs" +#pushd python/cuvs/cuvs +#pytest \ +# --cache-clear \ +# --junitxml="${RAPIDS_TESTS_DIR}/junit-cuvs.xml" \ +# --cov-config=../.coveragerc \ +# --cov=cuvs \ +# --cov-report=xml:"${RAPIDS_COVERAGE_DIR}/cuvs-coverage.xml" \ +# --cov-report=term \ +# test +#popd rapids-logger "Test script exiting with value: $EXITCODE" exit ${EXITCODE} diff --git a/ci/test_wheel_cuvs.sh b/ci/test_wheel_cuvs.sh index 6b213d399a..de75019155 100755 --- a/ci/test_wheel_cuvs.sh +++ b/ci/test_wheel_cuvs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. set -euo pipefail @@ -7,12 +7,12 @@ mkdir -p ./dist RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen ${RAPIDS_CUDA_VERSION})" RAPIDS_PY_WHEEL_NAME="cuvs_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./dist -# echo to expand wildcard before adding `[extra]` requires for pip -python -m pip install $(echo ./dist/cuvs*.whl)[test] - -# Run smoke tests for aarch64 pull requests -if [[ "$(arch)" == "aarch64" && "${RAPIDS_BUILD_TYPE}" == "pull-request" ]]; then - python ./ci/wheel_smoke_test_cuvs.py -else - python -m pytest ./python/cuvs/cuvs/test -fi +## echo to expand wildcard before adding `[extra]` requires for pip +#python -m pip install $(echo ./dist/cuvs*.whl)[test] +# +## Run smoke tests for aarch64 pull requests +#if [[ "$(arch)" == "aarch64" && "${RAPIDS_BUILD_TYPE}" == "pull-request" ]]; then +# python ./ci/wheel_smoke_test_cuvs.py +#else +# python -m pytest ./python/cuvs/cuvs/test +#fi diff --git a/docs/source/index.rst b/docs/source/index.rst index a161efb42d..bf97906101 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,9 +1,5 @@ -cuVS: GPU-accelerated Vector Search -=================================== - -.. image:: ../../img/raft-tech-stack-vss.png - :width: 800 - :alt: cuVS Tech Stack +cuVS: Vector Search and Clustering on the GPU +============================================= Useful Resources ################ @@ -19,7 +15,7 @@ Useful Resources What is cuVS? ############# -cuVS is a library for vector search on the GPU. +cuVS is a library for vector search and clustering on the GPU. .. toctree:: :maxdepth: 1 From 7ff6d70f79e8fe91772acaf831482ed718708984 Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Wed, 28 Feb 2024 20:15:32 +0100 Subject: [PATCH 002/623] Adding necessary codeowner groups (#36) Authors: - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Ray Douglass (https://github.com/raydouglass) --- .github/CODEOWNERS | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 407c5448e5..01dbcfc835 100755 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,12 +4,18 @@ cpp/ @rapidsai/cuvs-cpp-codeowners #python code owners python/ @rapidsai/cuvs-python-codeowners -#cmake code owners -**/CMakeLists.txt @rapidsai/cuvs-cmake-codeowners -**/cmake/ @rapidsai/cuvs-cmake-codeowners -python/setup.py @rapidsai/cuvs-cmake-codeowners -build.sh @rapidsai/cuvs-cmake-codeowners -**/build.sh @rapidsai/cuvs-cmake-codeowners +#rust code owners +rust/ @rapidsai/cuvs-rust-codeowners + +#docs code owners +docs/ @rapidsai/cuvs-docs-codeowners + +#build code owners +**/CMakeLists.txt @rapidsai/cuvs-build-codeowners +**/cmake/ @rapidsai/cuvs-build-codeowners +python/setup.py @rapidsai/cuvs-build-codeowners +build.sh @rapidsai/cuvs-build-codeowners +**/build.sh @rapidsai/cuvs-build-codeowners #build/ops code owners .github/ @rapidsai/ops-codeowners From 6040aa07c1b808769712f89e3077334a04a23bf7 Mon Sep 17 00:00:00 2001 From: Dante Gama Dessavre Date: Mon, 4 Mar 2024 09:18:38 -0600 Subject: [PATCH 003/623] CAGRA Python API (#35) PR does the following: - [x] Initial check in of new CAGRA Python API - [x] Initial check in of Cython using dlpack to call pylibcuvs - [x] Initial check in of pytests of CAGRA and docstrings - [x] Build updates for new files and algos - [x] Enable Python conda package and pytests run in CI - [x] Final cleanups Todos: - [x] Discussion regarding renaming `handle` to `resources` in Python API - [x] Testing exceptions handling while using C API or design strategy to enable that - [x] Finish todos that are not going to be solved in follow ups. Follow up PRs: - Moving cydlpack to RAFT. - Enable serialization - Enable update of dataset - Python API for other algorithms Authors: - Dante Gama Dessavre (https://github.com/dantegd) - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/35 --- .../cuda11.8-conda/devcontainer.json | 2 +- .devcontainer/cuda11.8-pip/devcontainer.json | 2 +- .../cuda12.0-conda/devcontainer.json | 2 +- .devcontainer/cuda12.0-pip/devcontainer.json | 2 +- .github/workflows/build.yaml | 12 +- .github/workflows/pr.yaml | 22 +- .github/workflows/test.yaml | 8 +- .pre-commit-config.yaml | 2 +- README.md | 150 +++++- VERSION | 2 +- build.sh | 15 +- ci/build_docs.sh | 10 +- ci/build_python.sh | 13 +- ci/build_wheel.sh | 2 + ci/build_wheel_cuvs.sh | 4 +- ci/release/update-version.sh | 8 +- ci/test_python.sh | 23 +- ci/test_wheel_cuvs.sh | 18 +- ci/wheel_smoke_test_cuvs.py | 47 +- .../all_cuda-118_arch-aarch64.yaml | 10 +- .../all_cuda-118_arch-x86_64.yaml | 10 +- ...64.yaml => all_cuda-122_arch-aarch64.yaml} | 14 +- ..._64.yaml => all_cuda-122_arch-x86_64.yaml} | 14 +- conda/recipes/cuvs/build.sh | 2 +- conda/recipes/cuvs/meta.yaml | 10 +- ..._template.sh => build_libcuvs_examples.sh} | 4 +- conda/recipes/libcuvs/meta.yaml | 6 +- cpp/CMakeLists.txt | 52 +- cpp/cmake/thirdparty/get_raft.cmake | 33 +- cpp/doxygen/Doxyfile | 2 +- cpp/{template => examples}/CMakeLists.txt | 0 cpp/{template => examples}/README.md | 8 +- cpp/{template => examples}/build.sh | 2 +- .../cmake/thirdparty/fetch_rapids.cmake | 4 +- .../cmake/thirdparty/get_cuvs.cmake | 0 .../src/cagra_example.cu | 0 cpp/{template => examples}/src/common.cuh | 2 +- .../cuvs/neighbors/{cagra_c.h => cagra.h} | 72 +-- cpp/src/neighbors/cagra_c.cpp | 58 +- cpp/test/CMakeLists.txt | 2 +- cpp/test/neighbors/ann_cagra_c.cu | 12 +- cpp/test/neighbors/c_api.c | 10 +- dependencies.yaml | 174 +++--- docs/source/build.md | 4 +- docs/source/conf.py | 4 +- docs/source/contributing.md | 8 +- docs/source/cpp_api/core_interop.rst | 6 +- docs/source/developer_guide.md | 8 +- fetch_rapids.cmake | 10 +- python/cuvs/CMakeLists.txt | 29 +- python/cuvs/README.md | 0 python/cuvs/cuvs/__init__.py | 2 +- python/cuvs/cuvs/common/CMakeLists.txt | 24 + python/cuvs/cuvs/common/__init__.pxd | 0 python/cuvs/cuvs/common/__init__.py | 18 + python/cuvs/cuvs/common/c_api.pxd | 32 ++ python/cuvs/cuvs/common/cydlpack.pxd | 70 +++ python/cuvs/cuvs/common/cydlpack.pyx | 102 ++++ python/cuvs/cuvs/common/temp_raft.py | 55 ++ python/cuvs/cuvs/neighbors/CMakeLists.txt | 15 + python/cuvs/cuvs/neighbors/__init__.pxd | 0 python/cuvs/cuvs/neighbors/__init__.py | 18 + .../cuvs/cuvs/neighbors/cagra/CMakeLists.txt | 24 + python/cuvs/cuvs/neighbors/cagra/__init__.pxd | 0 python/cuvs/cuvs/neighbors/cagra/__init__.py | 18 + python/cuvs/cuvs/neighbors/cagra/cagra.pxd | 95 ++++ python/cuvs/cuvs/neighbors/cagra/cagra.pyx | 502 ++++++++++++++++++ python/cuvs/cuvs/test/__init__py | 0 python/cuvs/cuvs/test/ann_utils.py | 35 ++ python/cuvs/cuvs/test/test_cagra.py | 175 ++++++ python/cuvs/cuvs/test/test_doctests.py | 116 ++++ python/cuvs/pyproject.toml | 32 +- python/cuvs/setup.cfg | 3 +- python/cuvs/setup.py | 37 -- 74 files changed, 1873 insertions(+), 414 deletions(-) rename conda/environments/{all_cuda-120_arch-aarch64.yaml => all_cuda-122_arch-aarch64.yaml} (85%) rename conda/environments/{all_cuda-120_arch-x86_64.yaml => all_cuda-122_arch-x86_64.yaml} (85%) rename conda/recipes/libcuvs/{build_libcuvs_template.sh => build_libcuvs_examples.sh} (61%) rename cpp/{template => examples}/CMakeLists.txt (100%) rename cpp/{template => examples}/README.md (82%) rename cpp/{template => examples}/build.sh (93%) rename cpp/{template => examples}/cmake/thirdparty/fetch_rapids.cmake (92%) rename cpp/{template => examples}/cmake/thirdparty/get_cuvs.cmake (100%) rename cpp/{template => examples}/src/cagra_example.cu (100%) rename cpp/{template => examples}/src/common.cuh (98%) rename cpp/include/cuvs/neighbors/{cagra_c.h => cagra.h} (79%) create mode 100644 python/cuvs/README.md create mode 100644 python/cuvs/cuvs/common/CMakeLists.txt create mode 100644 python/cuvs/cuvs/common/__init__.pxd create mode 100644 python/cuvs/cuvs/common/__init__.py create mode 100644 python/cuvs/cuvs/common/c_api.pxd create mode 100644 python/cuvs/cuvs/common/cydlpack.pxd create mode 100644 python/cuvs/cuvs/common/cydlpack.pyx create mode 100644 python/cuvs/cuvs/common/temp_raft.py create mode 100644 python/cuvs/cuvs/neighbors/CMakeLists.txt create mode 100644 python/cuvs/cuvs/neighbors/__init__.pxd create mode 100644 python/cuvs/cuvs/neighbors/__init__.py create mode 100644 python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt create mode 100644 python/cuvs/cuvs/neighbors/cagra/__init__.pxd create mode 100644 python/cuvs/cuvs/neighbors/cagra/__init__.py create mode 100644 python/cuvs/cuvs/neighbors/cagra/cagra.pxd create mode 100644 python/cuvs/cuvs/neighbors/cagra/cagra.pyx create mode 100644 python/cuvs/cuvs/test/__init__py create mode 100644 python/cuvs/cuvs/test/ann_utils.py create mode 100644 python/cuvs/cuvs/test/test_cagra.py create mode 100644 python/cuvs/cuvs/test/test_doctests.py delete mode 100644 python/cuvs/setup.py diff --git a/.devcontainer/cuda11.8-conda/devcontainer.json b/.devcontainer/cuda11.8-conda/devcontainer.json index 76ce8599a8..cefbea72b7 100644 --- a/.devcontainer/cuda11.8-conda/devcontainer.json +++ b/.devcontainer/cuda11.8-conda/devcontainer.json @@ -5,7 +5,7 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "conda", - "BASE": "rapidsai/devcontainers:24.02-cpp-llvm16-cuda11.8-mambaforge-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda11.8-mambaforge-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, diff --git a/.devcontainer/cuda11.8-pip/devcontainer.json b/.devcontainer/cuda11.8-pip/devcontainer.json index 3a126b36ef..05518805aa 100644 --- a/.devcontainer/cuda11.8-pip/devcontainer.json +++ b/.devcontainer/cuda11.8-pip/devcontainer.json @@ -5,7 +5,7 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.02-cpp-llvm16-cuda11.8-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda11.8-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, diff --git a/.devcontainer/cuda12.0-conda/devcontainer.json b/.devcontainer/cuda12.0-conda/devcontainer.json index 426aaef98f..3f89836e26 100644 --- a/.devcontainer/cuda12.0-conda/devcontainer.json +++ b/.devcontainer/cuda12.0-conda/devcontainer.json @@ -5,7 +5,7 @@ "args": { "CUDA": "12.0", "PYTHON_PACKAGE_MANAGER": "conda", - "BASE": "rapidsai/devcontainers:24.02-cpp-mambaforge-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.04-cpp-mambaforge-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, diff --git a/.devcontainer/cuda12.0-pip/devcontainer.json b/.devcontainer/cuda12.0-pip/devcontainer.json index 1ef2fdcb64..33f67cd3fb 100644 --- a/.devcontainer/cuda12.0-pip/devcontainer.json +++ b/.devcontainer/cuda12.0-pip/devcontainer.json @@ -5,7 +5,7 @@ "args": { "CUDA": "12.0", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.02-cpp-llvm16-cuda12.0-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda12.0-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f079b5e78a..34cf1f5b07 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,7 @@ concurrency: jobs: cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.04 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -37,7 +37,7 @@ jobs: python-build: needs: [cpp-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.04 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -46,7 +46,7 @@ jobs: upload-conda: needs: [cpp-build, python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.04 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -57,7 +57,7 @@ jobs: if: github.ref_type == 'branch' needs: python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.04 with: arch: "amd64" branch: ${{ inputs.branch }} @@ -69,7 +69,7 @@ jobs: sha: ${{ inputs.sha }} wheel-build-cuvs: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.04 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -79,7 +79,7 @@ jobs: wheel-publish-cuvs: needs: wheel-build-cuvs secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.04 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index b190a2a62f..5799f51087 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -23,29 +23,29 @@ jobs: - wheel-tests-cuvs - devcontainer secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.04 checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.04 with: enable_check_generated_files: false conda-cpp-build: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.04 with: build_type: pull-request node_type: cpu16 conda-cpp-tests: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.04 with: build_type: pull-request conda-cpp-checks: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.04 with: build_type: pull-request enable_check_symbols: true @@ -53,19 +53,19 @@ jobs: conda-python-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.04 with: build_type: pull-request conda-python-tests: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.04 with: build_type: pull-request docs-build: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.04 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -75,20 +75,20 @@ jobs: wheel-build-cuvs: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.04 with: build_type: pull-request script: ci/build_wheel_cuvs.sh wheel-tests-cuvs: needs: wheel-build-cuvs secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.04 with: build_type: pull-request script: ci/test_wheel_cuvs.sh devcontainer: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.04 with: build_command: | sccache -z; diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index acea5755f0..0e66113f24 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ on: jobs: conda-cpp-checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.04 with: build_type: nightly branch: ${{ inputs.branch }} @@ -26,7 +26,7 @@ jobs: symbol_exclusions: (void (thrust::|cub::)|_ZN\d+raft_cutlass) conda-cpp-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.04 with: build_type: nightly branch: ${{ inputs.branch }} @@ -34,7 +34,7 @@ jobs: sha: ${{ inputs.sha }} conda-python-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.04 with: build_type: nightly branch: ${{ inputs.branch }} @@ -42,7 +42,7 @@ jobs: sha: ${{ inputs.sha }} wheel-tests-cuvs: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.04 with: build_type: nightly branch: ${{ inputs.branch }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e3b1a38b3..a82fb74a57 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -99,7 +99,7 @@ repos: args: ["--toml", "pyproject.toml"] exclude: (?x)^(^CHANGELOG.md$) - repo: https://github.com/rapidsai/dependency-file-generator - rev: v1.5.1 + rev: v1.8.0 hooks: - id: rapids-dependency-file-generator args: ["--clean"] diff --git a/README.md b/README.md index 428ab04a95..dfba9eb4aa 100755 --- a/README.md +++ b/README.md @@ -1,19 +1,14 @@ #
 cuVS: Vector Search and Clustering on the GPU
-### NOTE: cuVS is currently being - ## Contents -
1. [Useful Resources](#useful-resources) 2. [What is cuVS?](#what-is-cuvs) -3. [Getting Started](#getting-started) -4. [Installing cuVS](#installing) +3. [Installing cuVS](#installing) +4. [Getting Started](#getting-started) 5. [Contributing](#contributing) 6. [References](#references) -
- ## Useful Resources - [cuVS Reference Documentation](https://docs.rapids.ai/api/cuvs/stable/): API Documentation. @@ -26,15 +21,152 @@ ## What is cuVS? -cuVS contains many algorithms for running approximate nearest neighbors and clustering on the GPU. +cuVS contains state-of-the-art implementations of several algorithms for running approximate nearest neighbors and clustering on the GPU. It can be used directly or through the various databases and other libraries that have integrated it. The primary goal of cuVS is to simplify the use of GPUs for vector similarity search and clustering. + +**Please note** that cuVS is a new library mostly derived from the approximate nearest neighbors and clustering algorithms in the [RAPIDS RAFT](https://github.com/rapidsai) library of data mining primitives. RAPIDS RAFT currently contains the most fully-featured versions of the approximate nearest neighbors and clustering algorithms in cuVS. We are in the process of migrating the algorithms from RAFT to cuVS, but if you are unsure of which to use, please consider the following: +1. RAFT contains C++ and Python APIs for all of the approximate nearest neighbors and clustering algorithms. +2. cuVS contains a growing support for different languages, including C, C++, Python, and Rust. We will be adding more language support to cuVS in the future but will not be improving the language support for RAFT. +3. Once all of RAFT's approximate nearest neighbors and clustering algorithms are moved to cuVS, the RAFT APIs will be deprecated and eventually removed altogether. Once removed, RAFT will become a lightweight header-only library. In the meantime, there's no harm in using RAFT if support for additional languages is not needed. + +## Installing cuVS + +cuVS comes with pre-built packages that can be installed through [conda](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html#managing-python). Different packages are available for the different languages supported by cuVS: + +| Python | C++ | C | Rust | +|--------|-----|---|------| +| `pycuvs`| `libcuvs` | `libcuvs_c` | `cuvs-rs` | + +### Stable release + +It is recommended to use [mamba](https://mamba.readthedocs.io/en/latest/installation/mamba-installation.html) to install the desired packages. The following command will install the Python package. You can substitute `pycuvs` for any of the packages in the table above: +```bash +mamba install -c conda-forge -c nvidia -c rapidsai pycuvs +``` + +### Nightlies +If installing a version that has not yet been released, the `rapidsai` channel can be replaced with `rapidsai-nightly`: +```bash +mamba install -c conda-forge -c nvidia -c rapidsai-nightly pycuvs=24.04* +``` + +Please see the [Build and Install Guide](docs/source/build.md) for more information on installing cuVS and building from source. ## Getting Started +The following code snippets train an approximate nearest neighbors index for the CAGRA algorithm. +### Python API + +```python +from cuvs.neighbors import cagra + +dataset = load_data() +index_params = cagra.IndexParams() + +index = cagra.build_index(build_params, dataset) +``` + +### C++ API + +```c++ +#include + +using namespace cuvs::neighbors; + +raft::device_matrix_view dataset = load_dataset(); +raft::device_resources res; + +cagra::index_params index_params; + +auto index = cagra::build(res, index_params, dataset); +``` + +For more example of the C++ APIs, refer to [cpp/examples](https://github.com/rapidsai/cuvs/tree/HEAD/cpp/examples) directory in the codebase. + +### C API + +```c +#include + +cuvsResources_t res; +cuvsCagraIndexParams_t index_params; +cuvsCagraIndex_t index; + +DLManagedTensor *dataset; +load_dataset(dataset); + +cuvsResourcesCreate(&res); +cuvsCagraIndexParamsCreate(&index_params); +cuvsCagraIndexCreate(&index); + +cuvsCagraBuild(res, index_params, dataset, index); + +cuvsCagraIndexDestroy(index); +cuvsCagraIndexParamsDestroy(index_params); +cuvsResourcesDestroy(res); +``` -## Installing cuVS ## Contributing +If you are interested in contributing to the cuVS library, please read our [Contributing guidelines](docs/source/contributing.md). Refer to the [Developer Guide](docs/source/developer_guide.md) for details on the developer guidelines, workflows, and principals. + ## References +When citing cuVS generally, please consider referencing this Github repository. +```bibtex +@misc{rapidsai, + title={Rapidsai/cuVS: Vector Search and Clustering on the GPU.}, + url={https://github.com/rapidsai/cuvs}, + journal={GitHub}, + publisher={Nvidia RAPIDS}, + author={Rapidsai}, + year={2024} +} +``` + +If citing CAGRA, please consider the following bibtex: +```bibtex +@misc{ootomo2023cagra, + title={CAGRA: Highly Parallel Graph Construction and Approximate Nearest Neighbor Search for GPUs}, + author={Hiroyuki Ootomo and Akira Naruse and Corey Nolet and Ray Wang and Tamas Feher and Yong Wang}, + year={2023}, + eprint={2308.15136}, + archivePrefix={arXiv}, + primaryClass={cs.DS} +} +``` + +If citing the k-selection routines, please consider the following bibtex: +```bibtex +@proceedings{10.1145/3581784, + title = {SC '23: Proceedings of the International Conference for High Performance Computing, Networking, Storage and Analysis}, + year = {2023}, + isbn = {9798400701092}, + publisher = {Association for Computing Machinery}, + address = {New York, NY, USA}, + abstract = {Started in 1988, the SC Conference has become the annual nexus for researchers and practitioners from academia, industry and government to share information and foster collaborations to advance the state of the art in High Performance Computing (HPC), Networking, Storage, and Analysis.}, + location = {, Denver, CO, USA, } +} +``` + +If citing the nearest neighbors descent API, please consider the following bibtex: +```bibtex +@inproceedings{10.1145/3459637.3482344, + author = {Wang, Hui and Zhao, Wan-Lei and Zeng, Xiangxiang and Yang, Jianye}, + title = {Fast K-NN Graph Construction by GPU Based NN-Descent}, + year = {2021}, + isbn = {9781450384469}, + publisher = {Association for Computing Machinery}, + address = {New York, NY, USA}, + url = {https://doi.org/10.1145/3459637.3482344}, + doi = {10.1145/3459637.3482344}, + abstract = {NN-Descent is a classic k-NN graph construction approach. It is still widely employed in machine learning, computer vision, and information retrieval tasks due to its efficiency and genericness. However, the current design only works well on CPU. In this paper, NN-Descent has been redesigned to adapt to the GPU architecture. A new graph update strategy called selective update is proposed. It reduces the data exchange between GPU cores and GPU global memory significantly, which is the processing bottleneck under GPU computation architecture. This redesign leads to full exploitation of the parallelism of the GPU hardware. In the meantime, the genericness, as well as the simplicity of NN-Descent, are well-preserved. Moreover, a procedure that allows to k-NN graph to be merged efficiently on GPU is proposed. It makes the construction of high-quality k-NN graphs for out-of-GPU-memory datasets tractable. Our approach is 100-250\texttimes{} faster than the single-thread NN-Descent and is 2.5-5\texttimes{} faster than the existing GPU-based approaches as we tested on million as well as billion scale datasets.}, + booktitle = {Proceedings of the 30th ACM International Conference on Information \& Knowledge Management}, + pages = {1929–1938}, + numpages = {10}, + keywords = {high-dimensional, nn-descent, gpu, k-nearest neighbor graph}, + location = {Virtual Event, Queensland, Australia}, + series = {CIKM '21} +} +``` diff --git a/VERSION b/VERSION index 3c6c5e2b70..4a2fe8aa57 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -24.02.00 +24.04.00 diff --git a/build.sh b/build.sh index 6dd250c51c..c6e09c5bf9 100755 --- a/build.sh +++ b/build.sh @@ -18,7 +18,7 @@ ARGS=$* # scripts, and that this script resides in the repo dir! REPODIR=$(cd $(dirname $0); pwd) -VALIDARGS="clean libcuvs python docs tests template clean --uninstall -v -g -n --compile-static-lib --allgpuarch --no-nvtx --show_depr_warn --incl-cache-stats --time -h" +VALIDARGS="clean libcuvs python docs tests examples clean --uninstall -v -g -n --compile-static-lib --allgpuarch --no-nvtx --show_depr_warn --incl-cache-stats --time -h" HELP="$0 [ ...] [ ...] [--cmake-args=\"\"] [--cache-tool=] [--limit-tests=] [--build-metrics=] where is: clean - remove all existing build artifacts and configuration (start over) @@ -27,7 +27,7 @@ HELP="$0 [ ...] [ ...] [--cmake-args=\"\"] [--cache-tool= is: -v - verbose build mode @@ -60,7 +60,7 @@ BUILD_DIRS="${LIBCUVS_BUILD_DIR} ${PYTHON_BUILD_DIR} ${CUVS_DASK_BUILD_DIR}" CMAKE_LOG_LEVEL="" VERBOSE_FLAG="" BUILD_ALL_GPU_ARCH=0 -BUILD_TESTS=OFF +BUILD_TESTS=ON BUILD_TYPE=Release COMPILE_LIBRARY=OFF INSTALL_TARGET=install @@ -305,6 +305,9 @@ if [[ ${CMAKE_TARGET} == "" ]]; then fi # Append `-DFIND_CUVS_CPP=ON` to EXTRA_CMAKE_ARGS unless a user specified the option. + + + SKBUILD_EXTRA_CMAKE_ARGS="${EXTRA_CMAKE_ARGS}" if [[ "${EXTRA_CMAKE_ARGS}" != *"DFIND_CUVS_CPP"* ]]; then SKBUILD_EXTRA_CMAKE_ARGS="${SKBUILD_EXTRA_CMAKE_ARGS} -DFIND_CUVS_CPP=ON" @@ -433,10 +436,10 @@ if hasArg docs; then fi ################################################################################ -# Initiate build for example CUVS application template (if needed) +# Initiate build for c++ examples (if needed) -if hasArg template; then - pushd ${REPODIR}/cpp/template +if hasArg examples; then + pushd ${REPODIR}/cpp/examples ./build.sh popd fi diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 1193285da5..983e973854 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -18,17 +18,13 @@ rapids-print-env rapids-logger "Downloading artifacts from previous jobs" CPP_CHANNEL=$(rapids-download-conda-from-s3 cpp) -PYTHON_CHANNEL=$(rapids-download-conda-from-s3 python) +#PYTHON_CHANNEL=$(rapids-download-conda-from-s3 python) rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ - --channel "${PYTHON_CHANNEL}" \ - libcuvs \ - libcuvs-headers \ - cuvs \ - raft-dask + libcuvs -export RAPIDS_VERSION_NUMBER="24.02" +export RAPIDS_VERSION_NUMBER="24.04" export RAPIDS_DOCS_DIR="$(mktemp -d)" rapids-logger "Build CPP docs" diff --git a/ci/build_python.sh b/ci/build_python.sh index 525d21c513..c50a413c6e 100755 --- a/ci/build_python.sh +++ b/ci/build_python.sh @@ -15,18 +15,17 @@ rapids-print-env rapids-logger "Begin py build" +package_name="cuvs" +package_dir="python" + CPP_CHANNEL=$(rapids-download-conda-from-s3 cpp) version=$(rapids-generate-version) git_commit=$(git rev-parse HEAD) -export RAPIDS_PACKAGE_VERSION=${version} +export RAPIDS_PACKAGE_VERSION=${version} echo "${version}" > VERSION -package_dir="python" -for package_name in cuvs raft-dask; do - underscore_package_name=$(echo "${package_name}" | tr "-" "_") - sed -i "/^__git_commit__/ s/= .*/= \"${git_commit}\"/g" "${package_dir}/${package_name}/${underscore_package_name}/_version.py" -done +sed -i "/^__git_commit__/ s/= .*/= \"${git_commit}\"/g" "${package_dir}/${package_name}/${package_name}/_version.py" # TODO: Remove `--no-test` flags once importing on a CPU # node works correctly @@ -34,3 +33,5 @@ rapids-conda-retry mambabuild \ --no-test \ --channel "${CPP_CHANNEL}" \ conda/recipes/cuvs + +rapids-upload-conda-to-s3 python diff --git a/ci/build_wheel.sh b/ci/build_wheel.sh index 3cc7f06c83..4f9f96d19c 100755 --- a/ci/build_wheel.sh +++ b/ci/build_wheel.sh @@ -37,6 +37,8 @@ if ! rapids-is-release-build; then fi sed -r -i "s/rmm(.*)\"/rmm${PACKAGE_CUDA_SUFFIX}\1${alpha_spec}\"/g" ${pyproject_file} +sed -r -i "s/pylibraft(.*)\"/pylibraft${PACKAGE_CUDA_SUFFIX}\1${alpha_spec}\"/g" ${pyproject_file} + if [[ $PACKAGE_CUDA_SUFFIX == "-cu12" ]]; then sed -i "s/cuda-python[<=>\.,0-9a]*/cuda-python>=12.0,<13.0a0/g" ${pyproject_file} diff --git a/ci/build_wheel_cuvs.sh b/ci/build_wheel_cuvs.sh index b4765be38a..238483b6e5 100755 --- a/ci/build_wheel_cuvs.sh +++ b/ci/build_wheel_cuvs.sh @@ -4,6 +4,6 @@ set -euo pipefail # Set up skbuild options. Enable sccache in skbuild config options -export SKBUILD_CONFIGURE_OPTIONS="-DRAFT_BUILD_WHEELS=ON -DDETECT_CONDA_ENV=OFF -DFIND_RAFT_CPP=OFF" +export SKBUILD_CONFIGURE_OPTIONS="-DDETECT_CONDA_ENV=OFF -DFIND_CUVS_CPP=OFF" -#ci/build_wheel.sh cuvs python/cuvs +ci/build_wheel.sh cuvs python/cuvs diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index f463aeb65b..b122e73bdf 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2020-2023, NVIDIA CORPORATION. +# Copyright (c) 2020-2024, NVIDIA CORPORATION. ######################## # RAFT Version Updater # ######################## @@ -38,7 +38,7 @@ function sed_runner() { sed_runner "s/set(RAPIDS_VERSION .*)/set(RAPIDS_VERSION \"${NEXT_SHORT_TAG}\")/g" cpp/CMakeLists.txt sed_runner "s/set(RAPIDS_VERSION .*)/set(RAPIDS_VERSION \"${NEXT_SHORT_TAG}\")/g" cpp/template/cmake/thirdparty/fetch_rapids.cmake -sed_runner "s/set(RAFT_VERSION .*)/set(RAFT_VERSION \"${NEXT_FULL_TAG}\")/g" cpp/CMakeLists.txt +sed_runner "s/set(CUVS_VERSION .*)/set(CUVS_VERSION \"${NEXT_FULL_TAG}\")/g" cpp/CMakeLists.txt sed_runner 's/'"cuvs_version .*)"'/'"cuvs_version ${NEXT_FULL_TAG})"'/g' python/cuvs/CMakeLists.txt sed_runner 's/'"branch-.*\/RAPIDS.cmake"'/'"branch-${NEXT_SHORT_TAG}\/RAPIDS.cmake"'/g' fetch_rapids.cmake @@ -57,6 +57,8 @@ DEPENDENCIES=( cuvs cuvs-cu11 cuvs-cu12 + pylibraft-cu11 + pylibraft-cu12 rmm rmm-cu11 rmm-cu12 @@ -85,7 +87,7 @@ sed_runner "s/RAPIDS_VERSION_NUMBER=\".*/RAPIDS_VERSION_NUMBER=\"${NEXT_SHORT_TA sed_runner "/^PROJECT_NUMBER/ s|\".*\"|\"${NEXT_SHORT_TAG}\"|g" cpp/doxygen/Doxyfile -sed_runner "/^set(RAFT_VERSION/ s|\".*\"|\"${NEXT_SHORT_TAG}\"|g" docs/source/build.md +sed_runner "/^set(CUVS_VERSION/ s|\".*\"|\"${NEXT_SHORT_TAG}\"|g" docs/source/build.md sed_runner "s|branch-[0-9][0-9].[0-9][0-9]|branch-${NEXT_SHORT_TAG}|g" docs/source/build.md sed_runner "/rapidsai\/raft/ s|branch-[0-9][0-9].[0-9][0-9]|branch-${NEXT_SHORT_TAG}|g" docs/source/developer_guide.md diff --git a/ci/test_python.sh b/ci/test_python.sh index 9f0c9d6ee7..17fbed4243 100755 --- a/ci/test_python.sh +++ b/ci/test_python.sh @@ -31,7 +31,7 @@ rapids-print-env rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ - libcuvs #cuvs + libcuvs cuvs rapids-logger "Check GPU usage" nvidia-smi @@ -40,17 +40,16 @@ EXITCODE=0 trap "EXITCODE=1" ERR set +e -#rapids-logger "pytest cuvs" -#pushd python/cuvs/cuvs -#pytest \ -# --cache-clear \ -# --junitxml="${RAPIDS_TESTS_DIR}/junit-cuvs.xml" \ -# --cov-config=../.coveragerc \ -# --cov=cuvs \ -# --cov-report=xml:"${RAPIDS_COVERAGE_DIR}/cuvs-coverage.xml" \ -# --cov-report=term \ -# test -#popd +rapids-logger "pytest cuvs" +pushd python/cuvs/cuvs +pytest \ + --cache-clear \ + --junitxml="${RAPIDS_TESTS_DIR}/junit-cuvs.xml" \ + --cov-config=../.coveragerc \ + --cov=cuvs \ + --cov-report=xml:"${RAPIDS_COVERAGE_DIR}/cuvs-coverage.xml" \ + --cov-report=term \ + test rapids-logger "Test script exiting with value: $EXITCODE" exit ${EXITCODE} diff --git a/ci/test_wheel_cuvs.sh b/ci/test_wheel_cuvs.sh index de75019155..364e00a7cf 100755 --- a/ci/test_wheel_cuvs.sh +++ b/ci/test_wheel_cuvs.sh @@ -7,12 +7,12 @@ mkdir -p ./dist RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen ${RAPIDS_CUDA_VERSION})" RAPIDS_PY_WHEEL_NAME="cuvs_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from-s3 ./dist -## echo to expand wildcard before adding `[extra]` requires for pip -#python -m pip install $(echo ./dist/cuvs*.whl)[test] -# -## Run smoke tests for aarch64 pull requests -#if [[ "$(arch)" == "aarch64" && "${RAPIDS_BUILD_TYPE}" == "pull-request" ]]; then -# python ./ci/wheel_smoke_test_cuvs.py -#else -# python -m pytest ./python/cuvs/cuvs/test -#fi +# echo to expand wildcard before adding `[extra]` requires for pip +python -m pip install $(echo ./dist/cuvs*.whl)[test] + +# Run smoke tests for aarch64 pull requests +if [[ "$(arch)" == "aarch64" && "${RAPIDS_BUILD_TYPE}" == "pull-request" ]]; then + python ./ci/wheel_smoke_test_cuvs.py +else + python -m pytest ./python/cuvs/cuvs/test +fi diff --git a/ci/wheel_smoke_test_cuvs.py b/ci/wheel_smoke_test_cuvs.py index 65b5fb8b2e..26ab5f6b50 100644 --- a/ci/wheel_smoke_test_cuvs.py +++ b/ci/wheel_smoke_test_cuvs.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,41 +13,30 @@ # limitations under the License. # +import cupy as cp import numpy as np -from scipy.spatial.distance import cdist -from pylibrat.common import Handle, Stream, device_ndarray -from cuvs.distance import pairwise_distance +from cuvs.neighbors import cagra +from pylibraft.common import Stream, DeviceResources if __name__ == "__main__": - metric = "euclidean" - n_rows = 1337 - n_cols = 1337 + n_samples = 1000 + n_features = 50 + n_queries = 1000 + k = 10 - input1 = np.random.random_sample((n_rows, n_cols)) - input1 = np.asarray(input1, order="C").astype(np.float64) + dataset = cp.random.random_sample((n_samples, + n_features)).astype(cp.float32) - output = np.zeros((n_rows, n_rows), dtype=np.float64) + build_params = cagra.IndexParams(metric="sqeuclidean", + build_algo="nn_descent") - expected = cdist(input1, input1, metric) + index = cagra.build_index(build_params, dataset) - expected[expected <= 1e-5] = 0.0 + distances, neighbors = cagra.search(cagra.SearchParams(), + index, dataset, + k) - input1_device = device_ndarray(input1) - output_device = None - - s2 = Stream() - handle = Handle(stream=s2) - ret_output = pairwise_distance( - input1_device, input1_device, output_device, metric, handle=handle - ) - handle.sync() - - output_device = ret_output - - actual = output_device.copy_to_host() - - actual[actual <= 1e-5] = 0.0 - - assert np.allclose(expected, actual, rtol=1e-4) + distances = cp.asarray(distances) + neighbors = cp.asarray(neighbors) diff --git a/conda/environments/all_cuda-118_arch-aarch64.yaml b/conda/environments/all_cuda-118_arch-aarch64.yaml index dbf92ec475..e4f9223784 100644 --- a/conda/environments/all_cuda-118_arch-aarch64.yaml +++ b/conda/environments/all_cuda-118_arch-aarch64.yaml @@ -36,18 +36,18 @@ dependencies: - libcusparse=11.7.5.86 - nccl>=2.9.9 - ninja -- numpy>=1.21 +- numpy>=1.23 - numpydoc - nvcc_linux-aarch64=11.8 - pre-commit - pydata-sphinx-theme -- pytest +- pylibraft==24.4.* - pytest-cov +- pytest==7.* - recommonmark -- rmm==24.2.* -- scikit-build>=0.13.1 +- rmm==24.4.* +- scikit-build-core>=0.7.0 - scikit-learn -- scipy - sphinx-copybutton - sphinx-markdown-tables - sysroot_linux-aarch64==2.17 diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 2fe184f964..a26314b229 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -36,18 +36,18 @@ dependencies: - libcusparse=11.7.5.86 - nccl>=2.9.9 - ninja -- numpy>=1.21 +- numpy>=1.23 - numpydoc - nvcc_linux-64=11.8 - pre-commit - pydata-sphinx-theme -- pytest +- pylibraft==24.4.* - pytest-cov +- pytest==7.* - recommonmark -- rmm==24.2.* -- scikit-build>=0.13.1 +- rmm==24.4.* +- scikit-build-core>=0.7.0 - scikit-learn -- scipy - sphinx-copybutton - sphinx-markdown-tables - sysroot_linux-64==2.17 diff --git a/conda/environments/all_cuda-120_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml similarity index 85% rename from conda/environments/all_cuda-120_arch-aarch64.yaml rename to conda/environments/all_cuda-122_arch-aarch64.yaml index 1b7f3908ad..91d55917ad 100644 --- a/conda/environments/all_cuda-120_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -17,7 +17,7 @@ dependencies: - cuda-nvtx-dev - cuda-profiler-api - cuda-python>=12.0,<13.0a0 -- cuda-version=12.0 +- cuda-version=12.2 - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 @@ -33,18 +33,18 @@ dependencies: - libcusparse-dev - nccl>=2.9.9 - ninja -- numpy>=1.21 +- numpy>=1.23 - numpydoc - pre-commit - pydata-sphinx-theme -- pytest +- pylibraft==24.4.* - pytest-cov +- pytest==7.* - recommonmark -- rmm==24.2.* -- scikit-build>=0.13.1 +- rmm==24.4.* +- scikit-build-core>=0.7.0 - scikit-learn -- scipy - sphinx-copybutton - sphinx-markdown-tables - sysroot_linux-aarch64==2.17 -name: all_cuda-120_arch-aarch64 +name: all_cuda-122_arch-aarch64 diff --git a/conda/environments/all_cuda-120_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml similarity index 85% rename from conda/environments/all_cuda-120_arch-x86_64.yaml rename to conda/environments/all_cuda-122_arch-x86_64.yaml index 3352279942..f27d131ffa 100644 --- a/conda/environments/all_cuda-120_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -17,7 +17,7 @@ dependencies: - cuda-nvtx-dev - cuda-profiler-api - cuda-python>=12.0,<13.0a0 -- cuda-version=12.0 +- cuda-version=12.2 - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 @@ -33,18 +33,18 @@ dependencies: - libcusparse-dev - nccl>=2.9.9 - ninja -- numpy>=1.21 +- numpy>=1.23 - numpydoc - pre-commit - pydata-sphinx-theme -- pytest +- pylibraft==24.4.* - pytest-cov +- pytest==7.* - recommonmark -- rmm==24.2.* -- scikit-build>=0.13.1 +- rmm==24.4.* +- scikit-build-core>=0.7.0 - scikit-learn -- scipy - sphinx-copybutton - sphinx-markdown-tables - sysroot_linux-64==2.17 -name: all_cuda-120_arch-x86_64 +name: all_cuda-122_arch-x86_64 diff --git a/conda/recipes/cuvs/build.sh b/conda/recipes/cuvs/build.sh index 81f762068b..767d066722 100644 --- a/conda/recipes/cuvs/build.sh +++ b/conda/recipes/cuvs/build.sh @@ -2,4 +2,4 @@ #!/usr/bin/env bash # This assumes the script is executed from the root of the repo directory -./build.sh python --no-nvtx +./build.sh python --no-nvtx -v diff --git a/conda/recipes/cuvs/meta.yaml b/conda/recipes/cuvs/meta.yaml index f22bd01d51..0902dd15a5 100644 --- a/conda/recipes/cuvs/meta.yaml +++ b/conda/recipes/cuvs/meta.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # Usage: # conda build . -c conda-forge -c numba -c rapidsai -c pytorch @@ -46,19 +46,19 @@ requirements: {% endif %} - cuda-version ={{ cuda_version }} - cython >=3.0.0 - - pylibraft {{ version }} + - dlpack >=0.8 + - pylibraft {{ minor_version }} - libcuvs {{ version }} - - numpy >=1.21 - python x.x - rmm ={{ minor_version }} - - scikit-build >=0.13.1 + - scikit-build-core >=0.7.0 - setuptools run: - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} {% if cuda_major == "11" %} - cudatoolkit {% endif %} - - pylibraft {{ version }} + - pylibraft {{ minor_version }} - libcuvs {{ version }} - python x.x - rmm ={{ minor_version }} diff --git a/conda/recipes/libcuvs/build_libcuvs_template.sh b/conda/recipes/libcuvs/build_libcuvs_examples.sh similarity index 61% rename from conda/recipes/libcuvs/build_libcuvs_template.sh rename to conda/recipes/libcuvs/build_libcuvs_examples.sh index bd7719af76..6286a530e6 100644 --- a/conda/recipes/libcuvs/build_libcuvs_template.sh +++ b/conda/recipes/libcuvs/build_libcuvs_examples.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # Just building template so we verify it uses libraft.so and fail if it doesn't build -./build.sh template +./build.sh examples diff --git a/conda/recipes/libcuvs/meta.yaml b/conda/recipes/libcuvs/meta.yaml index 4e437f3e62..4b6ff87e97 100644 --- a/conda/recipes/libcuvs/meta.yaml +++ b/conda/recipes/libcuvs/meta.yaml @@ -195,9 +195,9 @@ outputs: home: https://rapids.ai/ license: Apache-2.0 summary: libcuvs tests - - name: libcuvs-template + - name: libcuvs-examples version: {{ version }} - script: build_libcuvs_template.sh + script: build_libcuvs_examples.sh build: script_env: *script_env number: {{ GIT_DESCRIBE_NUMBER }} @@ -241,4 +241,4 @@ outputs: about: home: https://rapids.ai/ license: Apache-2.0 - summary: libcuvs template + summary: libcuvs examples diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 3c9cd6d1b7..73dec92a22 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -10,8 +10,8 @@ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express # or implied. See the License for the specific language governing permissions and limitations under # the License. -set(RAPIDS_VERSION "24.02") -set(CUVS_VERSION "24.02.00") +set(RAPIDS_VERSION "24.04") +set(CUVS_VERSION "24.04.00") cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) include(../fetch_rapids.cmake) @@ -28,12 +28,12 @@ set(lang_list "CXX") if(NOT BUILD_CPU_ONLY) include(rapids-cuda) - rapids_cuda_init_architectures(cuVS) + rapids_cuda_init_architectures(CUVS) list(APPEND lang_list "CUDA") endif() project( - cuVS + CUVS VERSION ${CUVS_VERSION} LANGUAGES ${lang_list} ) @@ -62,6 +62,7 @@ option(CUDA_ENABLE_LINEINFO "Enable the -lineinfo option for nvcc (useful for cuda-memcheck / profiler)" OFF ) option(CUDA_STATIC_RUNTIME "Statically link the CUDA toolkit runtime and libraries" OFF) +option(CUVS_USE_RAFT_STATIC "Build and statically link the RAFT libraries" OFF) option(CUDA_LOG_COMPILE_TIME "Write a log of compilation times to nvcc_compile_log.csv" OFF) option(DETECT_CONDA_ENV "Enable detection of conda environment for dependencies" ON) option(DISABLE_DEPRECATION_WARNINGS "Disable deprecaction warnings " ON) @@ -102,6 +103,7 @@ message(VERBOSE "cuVS: Enable nvtx markers: ${CUVS_NVTX}") message(VERBOSE "cuVS: Statically link the CUDA toolkit runtime and libraries: ${CUDA_STATIC_RUNTIME}" ) +message(VERBOSE "cuVS: Build and statically link RAFT libraries: ${CUVS_USE_RAFT_STATIC}") # Set RMM logging level set(RMM_LOGGING_LEVEL @@ -214,7 +216,16 @@ target_include_directories( if(NOT BUILD_CPU_ONLY) # Keep cuVS as lightweight as possible. Only CUDA libs and rmm should be used in global target. - target_link_libraries(cuvs PUBLIC raft::raft raft::compiled nvidia::cutlass::cutlass) + target_link_libraries(cuvs + PUBLIC + rmm::rmm + $<$>:raft::raft> + $<$>:raft::compiled> + PRIVATE + $<$:raft::raft> + $<$:raft::compiled_static> + nvidia::cutlass::cutlass + ) endif() # Endian detection @@ -269,11 +280,14 @@ endif() set_target_properties( cuvs - PROPERTIES CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON - CUDA_STANDARD 17 - CUDA_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON + PROPERTIES BUILD_RPATH "\$ORIGIN" + INSTALL_RPATH "\$ORIGIN" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CUDA_STANDARD 17 + CUDA_STANDARD_REQUIRED ON + INTERFACE_POSITION_INDEPENDENT_CODE ON + POSITION_INDEPENDENT_CODE ON ) target_compile_options( @@ -292,10 +306,13 @@ if(BUILD_C_LIBRARY) set_target_properties( cuvs_c - PROPERTIES CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON - EXPORT_NAME c_api + PROPERTIES BUILD_RPATH "\$ORIGIN" + INSTALL_RPATH "\$ORIGIN" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + POSITION_INDEPENDENT_CODE ON + INTERFACE_POSITION_INDEPENDENT_CODE ON + EXPORT_NAME c_api ) target_compile_options(cuvs_c PRIVATE "$<$:${CUVS_CXX_FLAGS}>") @@ -306,7 +323,12 @@ if(BUILD_C_LIBRARY) INTERFACE "$" ) - target_link_libraries(cuvs_c PUBLIC cuvs::cuvs) + target_link_libraries(cuvs_c + PUBLIC + cuvs::cuvs + PRIVATE + $<$:raft::raft> + ) # ensure CUDA symbols aren't relocated to the middle of the debug build binaries target_link_options(cuvs_c PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld") diff --git a/cpp/cmake/thirdparty/get_raft.cmake b/cpp/cmake/thirdparty/get_raft.cmake index d57d27312f..ace0165f71 100644 --- a/cpp/cmake/thirdparty/get_raft.cmake +++ b/cpp/cmake/thirdparty/get_raft.cmake @@ -21,9 +21,24 @@ function(find_and_configure_raft) cmake_parse_arguments(PKG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + if(PKG_CLONE_ON_PIN AND NOT PKG_PINNED_TAG STREQUAL "branch-${CUML_BRANCH_VERSION_raft}") + message(STATUS "cuVS: RAFT pinned tag found: ${PKG_PINNED_TAG}. Cloning raft locally.") + set(CPM_DOWNLOAD_raft ON) + elseif(PKG_USE_RAFT_STATIC AND (NOT CPM_raft_SOURCE)) + message(STATUS "cuVS: Cloning raft locally to build static libraries.") + set(CPM_DOWNLOAD_raft ON) + endif() + set(RAFT_COMPONENTS "") + if(PKG_COMPILE_LIBRARY) + if(NOT PKG_USE_RAFT_STATIC) string(APPEND RAFT_COMPONENTS " compiled") + set(RAFT_COMPILED_LIB raft::compiled PARENT_SCOPE) + else() + string(APPEND RAFT_COMPONENTS " compiled_static") + set(RAFT_COMPILED_LIB raft::compiled_static PARENT_SCOPE) + endif() endif() if(PKG_ENABLE_MNMG_DEPENDENCIES) @@ -39,15 +54,15 @@ function(find_and_configure_raft) INSTALL_EXPORT_SET cuvs-exports COMPONENTS ${RAFT_COMPONENTS} CPM_ARGS - GIT_REPOSITORY https://github.com/${PKG_FORK}/raft.git - GIT_TAG ${PKG_PINNED_TAG} - SOURCE_SUBDIR cpp - OPTIONS - "BUILD_TESTS OFF" - "BUILD_PRIMS_BENCH OFF" - "BUILD_ANN_BENCH OFF" - "RAFT_NVTX ${PKG_ENABLE_NVTX}" - "RAFT_COMPILE_LIBRARY ${PKG_COMPILE_LIBRARY}" + GIT_REPOSITORY https://github.com/${PKG_FORK}/raft.git + GIT_TAG ${PKG_PINNED_TAG} + SOURCE_SUBDIR cpp + OPTIONS + "BUILD_TESTS OFF" + "BUILD_PRIMS_BENCH OFF" + "BUILD_ANN_BENCH OFF" + "RAFT_NVTX ${PKG_ENABLE_NVTX}" + "RAFT_COMPILE_LIBRARY ${PKG_COMPILE_LIBRARY}" ) endfunction() diff --git a/cpp/doxygen/Doxyfile b/cpp/doxygen/Doxyfile index 0a2c7f8f61..94304afe02 100644 --- a/cpp/doxygen/Doxyfile +++ b/cpp/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "cuVS C++ API" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "24.02" +PROJECT_NUMBER = "24.04" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/cpp/template/CMakeLists.txt b/cpp/examples/CMakeLists.txt similarity index 100% rename from cpp/template/CMakeLists.txt rename to cpp/examples/CMakeLists.txt diff --git a/cpp/template/README.md b/cpp/examples/README.md similarity index 82% rename from cpp/template/README.md rename to cpp/examples/README.md index 5393c02296..125c6dba22 100644 --- a/cpp/template/README.md +++ b/cpp/examples/README.md @@ -1,14 +1,14 @@ -# Example CUVS Project Template +# cuVS C++ Examples This template project provides a drop-in sample to either start building a new application with, or using CUVS in an existing CMake project. -First, please refer to our [installation docs](https://docs.rapids.ai/api/cuvs/stable/build.html#cuda-gpu-requirements) for the minimum requirements to use CUVS. +First, please refer to our [installation docs](https://docs.rapids.ai/api/cuvs/stable/build.html#cuda-gpu-requirements) for the minimum requirements to use cuVS. Once the minimum requirements are satisfied, this example template application can be built with the provided `build.sh` script. This is a bash script that calls the appropriate CMake commands, so you can look into it to see the typical CMake based build workflow. -This directory (`CUVS_SOURCE/cpp/template`) can be copied directly in order to build a new application with CUVS. +This directory (`CUVS_SOURCE/cpp/examples`) can be copied directly in order to build a new application with CUVS. -CUVS can be integrated into an existing CMake project by copying the contents in the `configure rapids-cmake` and `configure cuvs` sections of the provided `CMakeLists.txt` into your project, along with `cmake/thirdparty/get_cuvs.cmake`. +cuVS can be integrated into an existing CMake project by copying the contents in the `configure rapids-cmake` and `configure cuvs` sections of the provided `CMakeLists.txt` into your project, along with `cmake/thirdparty/get_cuvs.cmake`. Make sure to link against the appropriate Cmake targets. Use `cuvs::cuvs` to utilize the shared library. diff --git a/cpp/template/build.sh b/cpp/examples/build.sh similarity index 93% rename from cpp/template/build.sh rename to cpp/examples/build.sh index 25ccb34618..7a948d9a8a 100755 --- a/cpp/template/build.sh +++ b/cpp/examples/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. # cuvs empty project template build script diff --git a/cpp/template/cmake/thirdparty/fetch_rapids.cmake b/cpp/examples/cmake/thirdparty/fetch_rapids.cmake similarity index 92% rename from cpp/template/cmake/thirdparty/fetch_rapids.cmake rename to cpp/examples/cmake/thirdparty/fetch_rapids.cmake index 15b6c43a6f..c22f586cad 100644 --- a/cpp/template/cmake/thirdparty/fetch_rapids.cmake +++ b/cpp/examples/cmake/thirdparty/fetch_rapids.cmake @@ -1,5 +1,5 @@ # ============================================================================= -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ # the License. # Use this variable to update RAPIDS and RAFT versions -set(RAPIDS_VERSION "24.02") +set(RAPIDS_VERSION "24.04") if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/RAFT_RAPIDS.cmake) file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-${RAPIDS_VERSION}/RAPIDS.cmake diff --git a/cpp/template/cmake/thirdparty/get_cuvs.cmake b/cpp/examples/cmake/thirdparty/get_cuvs.cmake similarity index 100% rename from cpp/template/cmake/thirdparty/get_cuvs.cmake rename to cpp/examples/cmake/thirdparty/get_cuvs.cmake diff --git a/cpp/template/src/cagra_example.cu b/cpp/examples/src/cagra_example.cu similarity index 100% rename from cpp/template/src/cagra_example.cu rename to cpp/examples/src/cagra_example.cu diff --git a/cpp/template/src/common.cuh b/cpp/examples/src/common.cuh similarity index 98% rename from cpp/template/src/common.cuh rename to cpp/examples/src/common.cuh index 0b72d3bf3b..757123cea5 100644 --- a/cpp/template/src/common.cuh +++ b/cpp/examples/src/common.cuh @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, NVIDIA CORPORATION. + * Copyright (c) 2023-2024, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/cpp/include/cuvs/neighbors/cagra_c.h b/cpp/include/cuvs/neighbors/cagra.h similarity index 79% rename from cpp/include/cuvs/neighbors/cagra_c.h rename to cpp/include/cuvs/neighbors/cagra.h index 59861b5029..64a26b9243 100644 --- a/cpp/include/cuvs/neighbors/cagra_c.h +++ b/cpp/include/cuvs/neighbors/cagra.h @@ -33,7 +33,7 @@ extern "C" { * @brief Enum to denote which ANN algorithm is used to build CAGRA graph * */ -enum cagraGraphBuildAlgo { +enum cuvsCagraGraphBuildAlgo { /* Use IVF-PQ to build all-neighbors knn graph */ IVF_PQ, /* Experimental, use NN-Descent to build all-neighbors knn graph */ @@ -44,18 +44,18 @@ enum cagraGraphBuildAlgo { * @brief Supplemental parameters to build CAGRA Index * */ -struct cagraIndexParams { +struct cuvsCagraIndexParams { /** Degree of input graph for pruning. */ size_t intermediate_graph_degree; /** Degree of output graph. */ size_t graph_degree; /** ANN algorithm to build knn graph. */ - enum cagraGraphBuildAlgo build_algo; + enum cuvsCagraGraphBuildAlgo build_algo; /** Number of Iterations to run if building with NN_DESCENT */ size_t nn_descent_niter; }; -typedef struct cagraIndexParams* cuvsCagraIndexParams_t; +typedef struct cuvsCagraIndexParams* cuvsCagraIndexParams_t; /** * @brief Allocate CAGRA Index params, and populate with default values @@ -77,7 +77,7 @@ cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t index); * @brief Enum to denote algorithm used to search CAGRA Index * */ -enum cagraSearchAlgo { +enum cuvsCagraSearchAlgo { /** For large batch sizes. */ SINGLE_CTA, /** For small batch sizes. */ @@ -90,13 +90,13 @@ enum cagraSearchAlgo { * @brief Enum to denote Hash Mode used while searching CAGRA index * */ -enum cagraHashMode { HASH, SMALL, AUTO_HASH }; +enum cuvsCagraHashMode { HASH, SMALL, AUTO_HASH }; /** * @brief Supplemental parameters to search CAGRA index * */ -struct cagraSearchParams { +struct cuvsCagraSearchParams { /** Maximum number of queries to search at the same time (batch size). Auto select when 0.*/ size_t max_queries; @@ -114,7 +114,7 @@ struct cagraSearchParams { // Reasonable default values are automatically chosen. /** Which search implementation to use. */ - enum cagraSearchAlgo algo; + enum cuvsCagraSearchAlgo algo; /** Number of threads used to calculate a single distance. 4, 8, 16, or 32. */ size_t team_size; @@ -128,7 +128,7 @@ struct cagraSearchParams { /** Thread block size. 0, 64, 128, 256, 512, 1024. Auto selection when 0. */ size_t thread_block_size; /** Hashmap type. Auto selection when AUTO. */ - enum cagraHashMode hashmap_mode; + enum cuvsCagraHashMode hashmap_mode; /** Lower limit of hashmap bit length. More than 8. */ size_t hashmap_min_bitlen; /** Upper limit of hashmap fill rate. More than 0.1, less than 0.9.*/ @@ -140,7 +140,7 @@ struct cagraSearchParams { uint64_t rand_xor_mask; }; -typedef struct cagraSearchParams* cuvsCagraSearchParams_t; +typedef struct cuvsCagraSearchParams* cuvsCagraSearchParams_t; /** * @brief Allocate CAGRA search params, and populate with default values @@ -166,24 +166,24 @@ typedef struct { uintptr_t addr; DLDataType dtype; -} cagraIndex; +} cuvsCagraIndex; -typedef cagraIndex* cagraIndex_t; +typedef cuvsCagraIndex* cuvsCagraIndex_t; /** * @brief Allocate CAGRA index * - * @param[in] index cagraIndex_t to allocate + * @param[in] index cuvsCagraIndex_t to allocate * @return cagraError_t */ -cuvsError_t cagraIndexCreate(cagraIndex_t* index); +cuvsError_t cuvsCagraIndexCreate(cuvsCagraIndex_t* index); /** * @brief De-allocate CAGRA index * - * @param[in] index cagraIndex_t to de-allocate + * @param[in] index cuvsCagraIndex_t to de-allocate */ -cuvsError_t cagraIndexDestroy(cagraIndex_t index); +cuvsError_t cuvsCagraIndexDestroy(cuvsCagraIndex_t index); /** * @brief Build a CAGRA index with a `DLManagedTensor` which has underlying @@ -195,7 +195,7 @@ cuvsError_t cagraIndexDestroy(cagraIndex_t index); * * @code {.c} * #include - * #include + * #include * * // Create cuvsResources_t * cuvsResources_t res; @@ -209,28 +209,28 @@ cuvsError_t cagraIndexDestroy(cagraIndex_t index); * cuvsError_t params_create_status = cuvsCagraIndexParamsCreate(¶ms); * * // Create CAGRA index - * cagraIndex_t index; - * cuvsError_t index_create_status = cagraIndexCreate(&index); + * cuvsCagraIndex_t index; + * cuvsError_t index_create_status = cuvsCagraIndexCreate(&index); * * // Build the CAGRA Index - * cuvsError_t build_status = cagraBuild(res, params, &dataset, index); + * cuvsError_t build_status = cuvsCagraBuild(res, params, &dataset, index); * * // de-allocate `params`, `index` and `res` * cuvsError_t params_destroy_status = cuvsCagraIndexParamsDestroy(params); - * cuvsError_t index_destroy_status = cagraIndexDestroy(index); + * cuvsError_t index_destroy_status = cuvsCagraIndexDestroy(index); * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); * @endcode * * @param[in] res cuvsResources_t opaque C handle * @param[in] params cuvsCagraIndexParams_t used to build CAGRA index * @param[in] dataset DLManagedTensor* training dataset - * @param[out] index cagraIndex_t Newly built CAGRA index + * @param[out] index cuvsCagraIndex_t Newly built CAGRA index * @return cuvsError_t */ -cuvsError_t cagraBuild(cuvsResources_t res, - cuvsCagraIndexParams_t params, - DLManagedTensor* dataset, - cagraIndex_t index); +cuvsError_t cuvsCagraBuild(cuvsResources_t res, + cuvsCagraIndexParams_t params, + DLManagedTensor* dataset, + cuvsCagraIndex_t index); /** * @brief Search a CAGRA index with a `DLManagedTensor` which has underlying @@ -244,7 +244,7 @@ cuvsError_t cagraBuild(cuvsResources_t res, * * @code {.c} * #include - * #include + * #include * * // Create cuvsResources_t * cuvsResources_t res; @@ -259,8 +259,8 @@ cuvsError_t cagraBuild(cuvsResources_t res, * cuvsCagraSearchParams_t params; * cuvsError_t params_create_status = cuvsCagraSearchParamsCreate(¶ms); * - * // Search the `index` built using `cagraBuild` - * cuvsError_t search_status = cagraSearch(res, params, index, queries, neighbors, distances); + * // Search the `index` built using `cuvsCagraBuild` + * cuvsError_t search_status = cuvsCagraSearch(res, params, index, queries, neighbors, distances); * * // de-allocate `params` and `res` * cuvsError_t params_destroy_status = cuvsCagraSearchParamsDestroy(params); @@ -269,17 +269,17 @@ cuvsError_t cagraBuild(cuvsResources_t res, * * @param[in] res cuvsResources_t opaque C handle * @param[in] params cuvsCagraSearchParams_t used to search CAGRA index - * @param[in] index cagraIndex which has been returned by `cagraBuild` + * @param[in] index cuvsCagraIndex which has been returned by `cuvsCagraBuild` * @param[in] queries DLManagedTensor* queries dataset to search * @param[out] neighbors DLManagedTensor* output `k` neighbors for queries * @param[out] distances DLManagedTensor* output `k` distances for queries */ -cuvsError_t cagraSearch(cuvsResources_t res, - cuvsCagraSearchParams_t params, - cagraIndex_t index, - DLManagedTensor* queries, - DLManagedTensor* neighbors, - DLManagedTensor* distances); +cuvsError_t cuvsCagraSearch(cuvsResources_t res, + cuvsCagraSearchParams_t params, + cuvsCagraIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances); #ifdef __cplusplus } diff --git a/cpp/src/neighbors/cagra_c.cpp b/cpp/src/neighbors/cagra_c.cpp index 638c9a23d8..70e268fb2f 100644 --- a/cpp/src/neighbors/cagra_c.cpp +++ b/cpp/src/neighbors/cagra_c.cpp @@ -24,13 +24,13 @@ #include #include +#include #include -#include namespace { template -void* _build(cuvsResources_t res, cagraIndexParams params, DLManagedTensor* dataset_tensor) +void* _build(cuvsResources_t res, cuvsCagraIndexParams params, DLManagedTensor* dataset_tensor) { auto dataset = dataset_tensor->dl_tensor; @@ -53,14 +53,13 @@ void* _build(cuvsResources_t res, cagraIndexParams params, DLManagedTensor* data auto mds = cuvs::core::from_dlpack(dataset_tensor); cuvs::neighbors::cagra::build_host(*res_ptr, build_params, mds, *index); } - return index; } template void _search(cuvsResources_t res, - cagraSearchParams params, - cagraIndex index, + cuvsCagraSearchParams params, + cuvsCagraIndex index, DLManagedTensor* queries_tensor, DLManagedTensor* neighbors_tensor, DLManagedTensor* distances_tensor) @@ -95,17 +94,17 @@ void _search(cuvsResources_t res, } // namespace -extern "C" cuvsError_t cagraIndexCreate(cagraIndex_t* index) +extern "C" cuvsError_t cuvsCagraIndexCreate(cuvsCagraIndex_t* index) { try { - *index = new cagraIndex{}; + *index = new cuvsCagraIndex{}; return CUVS_SUCCESS; } catch (...) { return CUVS_ERROR; } } -extern "C" cuvsError_t cagraIndexDestroy(cagraIndex_t index_c_ptr) +extern "C" cuvsError_t cuvsCagraIndexDestroy(cuvsCagraIndex_t index_c_ptr) { try { auto index = *index_c_ptr; @@ -130,10 +129,10 @@ extern "C" cuvsError_t cagraIndexDestroy(cagraIndex_t index_c_ptr) } } -extern "C" cuvsError_t cagraBuild(cuvsResources_t res, - cuvsCagraIndexParams_t params, - DLManagedTensor* dataset_tensor, - cagraIndex_t index) +extern "C" cuvsError_t cuvsCagraBuild(cuvsResources_t res, + cuvsCagraIndexParams_t params, + DLManagedTensor* dataset_tensor, + cuvsCagraIndex_t index) { try { auto dataset = dataset_tensor->dl_tensor; @@ -153,17 +152,20 @@ extern "C" cuvsError_t cagraBuild(cuvsResources_t res, dataset.dtype.bits); } return CUVS_SUCCESS; + } catch (const std::exception& ex) { + std::cerr << "Error occurred: " << ex.what() << std::endl; + return CUVS_ERROR; } catch (...) { return CUVS_ERROR; } } -extern "C" cuvsError_t cagraSearch(cuvsResources_t res, - cuvsCagraSearchParams_t params, - cagraIndex_t index_c_ptr, - DLManagedTensor* queries_tensor, - DLManagedTensor* neighbors_tensor, - DLManagedTensor* distances_tensor) +extern "C" cuvsError_t cuvsCagraSearch(cuvsResources_t res, + cuvsCagraSearchParams_t params, + cuvsCagraIndex_t index_c_ptr, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) { try { auto queries = queries_tensor->dl_tensor; @@ -197,6 +199,8 @@ extern "C" cuvsError_t cagraSearch(cuvsResources_t res, queries.dtype.bits); } return CUVS_SUCCESS; + } catch (const std::exception& ex) { + std::cerr << "Error occurred: " << ex.what() << std::endl; } catch (...) { return CUVS_ERROR; } @@ -205,10 +209,10 @@ extern "C" cuvsError_t cagraSearch(cuvsResources_t res, extern "C" cuvsError_t cuvsCagraIndexParamsCreate(cuvsCagraIndexParams_t* params) { try { - *params = new cagraIndexParams{.intermediate_graph_degree = 128, - .graph_degree = 64, - .build_algo = IVF_PQ, - .nn_descent_niter = 20}; + *params = new cuvsCagraIndexParams{.intermediate_graph_degree = 128, + .graph_degree = 64, + .build_algo = IVF_PQ, + .nn_descent_niter = 20}; return CUVS_SUCCESS; } catch (...) { return CUVS_ERROR; @@ -228,11 +232,11 @@ extern "C" cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t params extern "C" cuvsError_t cuvsCagraSearchParamsCreate(cuvsCagraSearchParams_t* params) { try { - *params = new cagraSearchParams{.itopk_size = 64, - .search_width = 1, - .hashmap_max_fill_rate = 0.5, - .num_random_samplings = 1, - .rand_xor_mask = 0x128394}; + *params = new cuvsCagraSearchParams{.itopk_size = 64, + .search_width = 1, + .hashmap_max_fill_rate = 0.5, + .num_random_samplings = 1, + .rand_xor_mask = 0x128394}; return CUVS_SUCCESS; } catch (...) { return CUVS_ERROR; diff --git a/cpp/test/CMakeLists.txt b/cpp/test/CMakeLists.txt index 609bc2d4ec..f33c141790 100644 --- a/cpp/test/CMakeLists.txt +++ b/cpp/test/CMakeLists.txt @@ -57,7 +57,7 @@ function(ConfigureTest) ) set_target_properties( ${TEST_NAME} - PROPERTIES RUNTIME_OUTPUT_DIRECTORY "$" + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "$" INSTALL_RPATH "\$ORIGIN/../../../lib" CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON diff --git a/cpp/test/neighbors/ann_cagra_c.cu b/cpp/test/neighbors/ann_cagra_c.cu index 4870ac3b83..6e3a3cbd1f 100644 --- a/cpp/test/neighbors/ann_cagra_c.cu +++ b/cpp/test/neighbors/ann_cagra_c.cu @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -56,13 +56,13 @@ TEST(CagraC, BuildSearch) dataset_tensor.dl_tensor.strides = nullptr; // create index - cagraIndex_t index; - cagraIndexCreate(&index); + cuvsCagraIndex_t index; + cuvsCagraIndexCreate(&index); // build index cuvsCagraIndexParams_t build_params; cuvsCagraIndexParamsCreate(&build_params); - cagraBuild(res, build_params, &dataset_tensor, index); + cuvsCagraBuild(res, build_params, &dataset_tensor, index); // create queries DLTensor float* queries_d; @@ -113,7 +113,7 @@ TEST(CagraC, BuildSearch) // search index cuvsCagraSearchParams_t search_params; cuvsCagraSearchParamsCreate(&search_params); - cagraSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); + cuvsCagraSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); // verify output ASSERT_TRUE(cuvs::devArrMatchHost(neighbors_exp, neighbors_d, 4, cuvs::Compare())); @@ -128,6 +128,6 @@ TEST(CagraC, BuildSearch) // de-allocate index and res cuvsCagraSearchParamsDestroy(search_params); cuvsCagraIndexParamsDestroy(build_params); - cagraIndexDestroy(index); + cuvsCagraIndexDestroy(index); cuvsResourcesDestroy(res); } diff --git a/cpp/test/neighbors/c_api.c b/cpp/test/neighbors/c_api.c index d4f5ad08e0..0c476e95bd 100644 --- a/cpp/test/neighbors/c_api.c +++ b/cpp/test/neighbors/c_api.c @@ -15,17 +15,17 @@ */ #include -#include +#include #include #include int main() { - // simple smoke test to make sure that we can compile the cagra_c.h API + // simple smoke test to make sure that we can compile the cagra.h API // using a c compiler. This isn't aiming to be a full test, just checking // that the exposed C-API is valid C code and doesn't contain C++ features - cagraIndex_t index; - cagraIndexCreate(&index); - cagraIndexDestroy(index); + cuvsCagraIndex_t index; + cuvsCagraIndexCreate(&index); + cuvsCagraIndexDestroy(index); return 0; } diff --git a/dependencies.yaml b/dependencies.yaml index 6f9f10535a..f17b84dffa 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -3,33 +3,34 @@ files: all: output: conda matrix: - cuda: ["11.8", "12.0"] + cuda: ["11.8", "12.2"] arch: [x86_64, aarch64] includes: - build - - build_cuvs - - cudatoolkit + - build_py_cuvs + - cuda + - cuda_version - develop - checks - build_wheels - test_libcuvs - docs - - run_cuvs + - run_py_cuvs - test_python_common - - test_cuvs + - test_py_cuvs - cupy test_cpp: output: none includes: - - cudatoolkit + - cuda_version - test_libcuvs test_python: output: none includes: - - cudatoolkit + - cuda_version - py_version - test_python_common - - test_cuvs + - test_py_cuvs - cupy checks: output: none @@ -39,28 +40,27 @@ files: docs: output: none includes: - - test_cuvs + - cuda_version - cupy - - cudatoolkit - docs - py_version - py_build_cuvs: + - test_py_cuvs + py_build_py_cuvs: output: pyproject pyproject_dir: python/cuvs extras: table: build-system includes: - build - - build_cuvs - - build_wheels - py_run_cuvs: + - build_py_cuvs + py_run_py_cuvs: output: pyproject pyproject_dir: python/cuvs extras: table: project includes: - - run_cuvs - py_test_cuvs: + - run_py_cuvs + py_test_py_cuvs: output: pyproject pyproject_dir: python/cuvs extras: @@ -68,7 +68,7 @@ files: key: test includes: - test_python_common - - test_cuvs + - test_py_cuvs - cupy channels: - rapidsai @@ -84,12 +84,15 @@ dependencies: - &cmake_ver cmake>=3.26.4 - cython>=3.0.0 - ninja - - scikit-build>=0.13.1 - output_types: [conda] packages: - c-compiler - cxx-compiler - nccl>=2.9.9 + - scikit-build-core>=0.7.0 + - output_types: [requirements, pyproject] + packages: + - scikit-build-core[pyproject]>=0.7.0 specific: - output_types: conda matrices: @@ -105,8 +108,8 @@ dependencies: - sysroot_linux-aarch64==2.17 - output_types: conda matrices: - - matrix: {cuda: "12.0"} - packages: [cuda-version=12.0, cuda-nvcc] + - matrix: {cuda: "12.*"} + packages: [cuda-nvcc] - matrix: {cuda: "11.8", arch: x86_64} packages: [nvcc_linux-64=11.8] - matrix: {cuda: "11.8", arch: aarch64} @@ -124,21 +127,23 @@ dependencies: - matrix: {cuda: "11.2", arch: aarch64} packages: [nvcc_linux-aarch64=11.2] - build_cuvs: + build_py_cuvs: common: - output_types: [conda] packages: - - &rmm_conda rmm==24.2.* + - &rmm_conda rmm==24.4.* + - &pylibraft_conda pylibraft==24.4.* - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file # This index is needed for rmm-cu{11,12}. - --extra-index-url=https://pypi.nvidia.com + - --extra-index-url=https://pypi.anaconda.org/rapidsai-wheels-nightly/simple specific: - output_types: [conda, requirements, pyproject] matrices: - matrix: - cuda: "12.0" + cuda: "12.*" packages: - &cuda_python12 cuda-python>=12.0,<13.0a0 - matrix: # All CUDA 11 versions @@ -146,18 +151,15 @@ dependencies: - &cuda_python11 cuda-python>=11.7.1,<12.0a0 - output_types: [requirements, pyproject] matrices: - - matrix: {cuda: "12.2"} - packages: &build_cuvs_packages_cu12 - - &rmm_cu12 rmm-cu12==24.2.* - - {matrix: {cuda: "12.1"}, packages: *build_cuvs_packages_cu12} - - {matrix: {cuda: "12.0"}, packages: *build_cuvs_packages_cu12} - - matrix: {cuda: "11.8"} - packages: &build_cuvs_packages_cu11 - - &rmm_cu11 rmm-cu11==24.2.* - - {matrix: {cuda: "11.5"}, packages: *build_cuvs_packages_cu11} - - {matrix: {cuda: "11.4"}, packages: *build_cuvs_packages_cu11} - - {matrix: {cuda: "11.2"}, packages: *build_cuvs_packages_cu11} - - {matrix: null, packages: [*rmm_conda] } + - matrix: {cuda: "12.*"} + packages: + - &rmm_cu12 rmm-cu12==24.4.* + - &pylibraft_cu12 pylibraft-cu12==24.4.* + - matrix: {cuda: "11.*"} + packages: + - &rmm_cu11 rmm-cu11==24.4.* + - &pylibraft_cu11 pylibraft-cu11==24.4.* + - {matrix: null, packages: [*rmm_conda, *pylibraft_conda] } checks: common: - output_types: [conda, requirements] @@ -169,14 +171,41 @@ dependencies: packages: - clang==16.0.6 - clang-tools=16.0.6 - cudatoolkit: + cuda_version: specific: - output_types: conda matrices: + - matrix: + cuda: "11.2" + packages: + - cuda-version=11.2 + - matrix: + cuda: "11.4" + packages: + - cuda-version=11.4 + - matrix: + cuda: "11.5" + packages: + - cuda-version=11.5 + - matrix: + cuda: "11.8" + packages: + - cuda-version=11.8 - matrix: cuda: "12.0" packages: - cuda-version=12.0 + - matrix: + cuda: "12.2" + packages: + - cuda-version=12.2 + cuda: + specific: + - output_types: conda + matrices: + - matrix: + cuda: "12.*" + packages: - cuda-nvtx-dev - cuda-cudart-dev - cuda-profiler-api @@ -187,7 +216,6 @@ dependencies: - matrix: cuda: "11.8" packages: - - cuda-version=11.8 - cudatoolkit - cuda-nvtx=11.8 - cuda-profiler-api=11.8.86 @@ -202,7 +230,6 @@ dependencies: - matrix: cuda: "11.5" packages: - - cuda-version=11.5 - cudatoolkit - cuda-nvtx=11.5 - cuda-profiler-api>=11.4.240,<=11.8.86 # use any `11.x` version since pkg is missing several CUDA/arch packages @@ -217,7 +244,6 @@ dependencies: - matrix: cuda: "11.4" packages: - - cuda-version=11.4 - cudatoolkit - &cudanvtx114 cuda-nvtx=11.4 - cuda-profiler-api>=11.4.240,<=11.8.86 # use any `11.x` version since pkg is missing several CUDA/arch packages @@ -232,7 +258,6 @@ dependencies: - matrix: cuda: "11.2" packages: - - cuda-version=11.2 - cudatoolkit - *cudanvtx114 - cuda-profiler-api>=11.4.240,<=11.8.86 # use any `11.x` version since pkg is missing several CUDA/arch packages @@ -255,34 +280,12 @@ dependencies: specific: - output_types: [requirements, pyproject] matrices: - # All CUDA 12 + x86_64 versions - - matrix: {cuda: "12.2", arch: x86_64} - packages: &cupy_packages_cu12_x86_64 - - &cupy_cu12_x86_64 cupy-cuda12x>=12.0.0 - - {matrix: {cuda: "12.1", arch: x86_64}, packages: *cupy_packages_cu12_x86_64} - - {matrix: {cuda: "12.0", arch: x86_64}, packages: *cupy_packages_cu12_x86_64} - # All CUDA 12 + aarch64 versions - - matrix: {cuda: "12.2", arch: aarch64} - packages: &cupy_packages_cu12_aarch64 - - &cupy_cu12_aarch64 cupy-cuda12x -f https://pip.cupy.dev/aarch64 # TODO: Verify that this works. - - {matrix: {cuda: "12.1", arch: aarch64}, packages: *cupy_packages_cu12_aarch64} - - {matrix: {cuda: "12.0", arch: aarch64}, packages: *cupy_packages_cu12_aarch64} - - # All CUDA 11 + x86_64 versions - - matrix: {cuda: "11.8", arch: x86_64} - packages: &cupy_packages_cu11_x86_64 + - matrix: {cuda: "12.*"} + packages: + - cupy-cuda12x>=12.0.0 + - matrix: {cuda: "11.*"} + packages: - cupy-cuda11x>=12.0.0 - - {matrix: {cuda: "11.5", arch: x86_64}, packages: *cupy_packages_cu11_x86_64} - - {matrix: {cuda: "11.4", arch: x86_64}, packages: *cupy_packages_cu11_x86_64} - - {matrix: {cuda: "11.2", arch: x86_64}, packages: *cupy_packages_cu11_x86_64} - - # All CUDA 11 + aarch64 versions - - matrix: {cuda: "11.8", arch: aarch64} - packages: &cupy_packages_cu11_aarch64 - - cupy-cuda11x -f https://pip.cupy.dev/aarch64 # TODO: Verify that this works. - - {matrix: {cuda: "11.5", arch: aarch64}, packages: *cupy_packages_cu11_aarch64} - - {matrix: {cuda: "11.4", arch: aarch64}, packages: *cupy_packages_cu11_aarch64} - - {matrix: {cuda: "11.2", arch: aarch64}, packages: *cupy_packages_cu11_aarch64} - {matrix: null, packages: [cupy-cuda11x>=12.0.0]} test_libcuvs: @@ -324,26 +327,32 @@ dependencies: packages: - python=3.10 - matrix: + py: "3.11" packages: - - python>=3.9,<3.11 - run_cuvs: + - python=3.11 + - matrix: + packages: + - python>=3.9,<3.12 + run_py_cuvs: common: - output_types: [conda, pyproject] packages: - - &numpy numpy>=1.21 + - &numpy numpy>=1.23 - output_types: [conda] packages: - *rmm_conda + - *pylibraft_conda - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file # This index is needed for cudf and rmm. - --extra-index-url=https://pypi.nvidia.com + - --extra-index-url=https://pypi.anaconda.org/rapidsai-wheels-nightly/simple specific: - output_types: [conda, requirements, pyproject] matrices: - matrix: - cuda: "12.0" + cuda: "12.*" packages: - *cuda_python12 - matrix: # All CUDA 11 versions @@ -351,27 +360,22 @@ dependencies: - *cuda_python11 - output_types: [requirements, pyproject] matrices: - - matrix: {cuda: "12.2"} - packages: &run_cuvs_packages_cu12 - - *rmm_cu12 - - {matrix: {cuda: "12.1"}, packages: *run_cuvs_packages_cu12} - - {matrix: {cuda: "12.0"}, packages: *run_cuvs_packages_cu12} - - matrix: {cuda: "11.8"} - packages: &run_cuvs_packages_cu11 - - *rmm_cu11 - - {matrix: {cuda: "11.5"}, packages: *run_cuvs_packages_cu11} - - {matrix: {cuda: "11.4"}, packages: *run_cuvs_packages_cu11} - - {matrix: {cuda: "11.2"}, packages: *run_cuvs_packages_cu11} + - matrix: {cuda: "12.*"} + packages: + - *pylibraft_cu12 + - matrix: {cuda: "11.*"} + packages: + - *pylibraft_cu11 - {matrix: null, packages: [*rmm_conda]} test_python_common: common: - output_types: [conda, requirements, pyproject] packages: - - pytest + - pytest==7.* - pytest-cov - test_cuvs: + test_py_cuvs: common: - output_types: [conda, requirements, pyproject] packages: + - *pylibraft_conda - scikit-learn - - scipy diff --git a/docs/source/build.md b/docs/source/build.md index ae7734d0ed..31de69b461 100644 --- a/docs/source/build.md +++ b/docs/source/build.md @@ -56,7 +56,7 @@ You can also install the conda packages individually using the `mamba` command a mamba install -c rapidsai -c conda-forge -c nvidia libraft libraft-headers cuda-version=12.0 ``` -If installing the C++ APIs Please see [using libraft](https://docs.rapids.ai/api/raft/nightly/using_libraft/) for more information on using the pre-compiled shared library. You can also refer to the [example C++ template project](https://github.com/rapidsai/raft/tree/branch-24.02/cpp/template) for a ready-to-go CMake configuration that you can drop into your project and build against installed RAFT development artifacts above. +If installing the C++ APIs Please see [using libraft](https://docs.rapids.ai/api/raft/nightly/using_libraft/) for more information on using the pre-compiled shared library. You can also refer to the [example C++ template project](https://github.com/rapidsai/raft/tree/branch-24.04/cpp/template) for a ready-to-go CMake configuration that you can drop into your project and build against installed RAFT development artifacts above. ## Installing Python through Pip @@ -315,4 +315,4 @@ The `raft::raft` CMake target is made available when including RAFT into your CM |-------------|---------------------|----------------------------------------------------------|----------------------------------------| | n/a | `raft::raft` | Full RAFT header library | CUDA toolkit, RMM, NVTX, CCCL, CUTLASS | | compiled | `raft::compiled` | Pre-compiled template instantiations and runtime library | raft::raft | -| distributed | `raft::distributed` | Dependencies for `raft::comms` APIs | raft::raft, UCX, NCCL \ No newline at end of file +| distributed | `raft::distributed` | Dependencies for `raft::comms` APIs | raft::raft, UCX, NCCL diff --git a/docs/source/conf.py b/docs/source/conf.py index c09ab953f2..1a5c9dfe83 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -67,9 +67,9 @@ # built documents. # # The short X.Y version. -version = '24.02' +version = '24.04' # The full version, including alpha/beta/rc tags. -release = '24.02.00' +release = '24.04.00' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/contributing.md b/docs/source/contributing.md index 090fd834b3..c426ce5340 100755 --- a/docs/source/contributing.md +++ b/docs/source/contributing.md @@ -1,9 +1,9 @@ # Contributing -If you are interested in contributing to CUVS, your contributions will fall +If you are interested in contributing to cuVS, your contributions will fall into three categories: 1. You want to report a bug, feature request, or documentation issue - - File an [issue](https://github.com/rapidsai/CUVS/issues/new/choose) + - File an [issue](https://github.com/rapidsai/cuvs/issues/new/choose) describing what you encountered or what you want to see changed. - The RAPIDS team will evaluate the issues and triage them, scheduling them for a release. If you believe the issue needs priority attention @@ -26,10 +26,10 @@ into three categories: 1. Read the project's [README.md](https://github.com/rapidsai/cuvs) to learn how to setup the development environment 2. Find an issue to work on. The best way is to look for the [good first issue](https://github.com/rapidsai/CUVS/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) - or [help wanted](https://github.com/rapidsai/CUVS/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) labels + or [help wanted](https://github.com/rapidsai/cuvs/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) labels 3. Comment on the issue saying you are going to work on it 4. Code! Make sure to update unit tests! -5. When done, [create your pull request](https://github.com/rapidsai/CUVS/compare) +5. When done, [create your pull request](https://github.com/rapidsai/cuvs/compare) 6. Verify that CI passes all [status checks](https://help.github.com/articles/about-status-checks/). Fix if needed 7. Wait for other developers to review your code and update code as needed 8. Once reviewed and approved, a RAPIDS developer will merge your pull request diff --git a/docs/source/cpp_api/core_interop.rst b/docs/source/cpp_api/core_interop.rst index 034030db5c..b2ef05f275 100644 --- a/docs/source/cpp_api/core_interop.rst +++ b/docs/source/cpp_api/core_interop.rst @@ -6,11 +6,11 @@ Interop :class: highlight -``#include `` +``#include `` -namespace *raft::core* +namespace *cuvs::core* .. doxygengroup:: interop - :project: RAFT + :project: cuvs :members: :content-only: diff --git a/docs/source/developer_guide.md b/docs/source/developer_guide.md index c5bcd03f69..d29130add0 100644 --- a/docs/source/developer_guide.md +++ b/docs/source/developer_guide.md @@ -187,7 +187,7 @@ RAFT relies on `clang-format` to enforce code style across all C++ and CUDA sour 1. Do not split empty functions/records/namespaces. 2. Two-space indentation everywhere, including the line continuations. 3. Disable reflowing of comments. - The reasons behind these deviations from the Google style guide are given in comments [here](https://github.com/rapidsai/raft/blob/branch-24.02/cpp/.clang-format). + The reasons behind these deviations from the Google style guide are given in comments [here](https://github.com/rapidsai/raft/blob/branch-24.04/cpp/.clang-format). [`doxygen`](https://doxygen.nl/) is used as documentation generator and also as a documentation linter. In order to run doxygen as a linter on C++/CUDA code, run @@ -205,7 +205,7 @@ you can run `codespell -i 3 -w .` from the repository root directory. This will bring up an interactive prompt to select which spelling fixes to apply. ### #include style -[include_checker.py](https://github.com/rapidsai/raft/blob/branch-24.02/cpp/scripts/include_checker.py) is used to enforce the include style as follows: +[include_checker.py](https://github.com/rapidsai/raft/blob/branch-24.04/cpp/scripts/include_checker.py) is used to enforce the include style as follows: 1. `#include "..."` should be used for referencing local files only. It is acceptable to be used for referencing files in a sub-folder/parent-folder of the same algorithm, but should never be used to include files in other algorithms or between algorithms and the primitives or other dependencies. 2. `#include <...>` should be used for referencing everything else @@ -215,7 +215,7 @@ python ./cpp/scripts/include_checker.py --inplace [cpp/include cpp/test ... list ``` ### Copyright header -[copyright.py](https://github.com/rapidsai/raft/blob/branch-24.02/ci/checks/copyright.py) checks the Copyright header for all git-modified files +[copyright.py](https://github.com/rapidsai/raft/blob/branch-24.04/ci/checks/copyright.py) checks the Copyright header for all git-modified files Manually, you can run the following to bulk-fix the header if only the years need to be updated: ```bash @@ -229,7 +229,7 @@ Call CUDA APIs via the provided helper macros `RAFT_CUDA_TRY`, `RAFT_CUBLAS_TRY` ## Logging ### Introduction -Anything and everything about logging is defined inside [logger.hpp](https://github.com/rapidsai/raft/blob/branch-24.02/cpp/include/raft/core/logger.hpp). It uses [spdlog](https://github.com/gabime/spdlog) underneath, but this information is transparent to all. +Anything and everything about logging is defined inside [logger.hpp](https://github.com/rapidsai/raft/blob/branch-24.04/cpp/include/raft/core/logger.hpp). It uses [spdlog](https://github.com/gabime/spdlog) underneath, but this information is transparent to all. ### Usage ```cpp diff --git a/fetch_rapids.cmake b/fetch_rapids.cmake index ca871c5759..330270d662 100644 --- a/fetch_rapids.cmake +++ b/fetch_rapids.cmake @@ -1,5 +1,5 @@ # ============================================================================= -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # in compliance with the License. You may obtain a copy of the License at @@ -11,10 +11,10 @@ # or implied. See the License for the specific language governing permissions and limitations under # the License. # ============================================================================= -if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/RAFT_RAPIDS.cmake) - file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-24.02/RAPIDS.cmake - ${CMAKE_CURRENT_BINARY_DIR}/RAFT_RAPIDS.cmake +if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS.cmake) + file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-24.04/RAPIDS.cmake + ${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS.cmake ) endif() -include(${CMAKE_CURRENT_BINARY_DIR}/RAFT_RAPIDS.cmake) +include(${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS.cmake) diff --git a/python/cuvs/CMakeLists.txt b/python/cuvs/CMakeLists.txt index ca9da8a9d3..5a5efe8f9c 100644 --- a/python/cuvs/CMakeLists.txt +++ b/python/cuvs/CMakeLists.txt @@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) include(../../fetch_rapids.cmake) -set(cuvs_version 24.02.00) +set(cuvs_version 24.04.00) # We always need CUDA for cuvs because the cuvs dependency brings in a header-only cuco dependency # that enables CUDA unconditionally. @@ -37,29 +37,44 @@ option(FIND_CUVS_CPP "Search for existing CUVS C++ installations before defaulti OFF ) +message("- FIND_CUVS_CPP: ${FIND_CUVS_CPP}") + +include(../../fetch_rapids.cmake) +include(rapids-cmake) +include(rapids-cpm) +include(rapids-cython-core) +include(rapids-export) +include(rapids-find) + +rapids_cpm_init() + # If the user requested it we attempt to find CUVS. if(FIND_CUVS_CPP) find_package(cuvs ${cuvs_version}) + include(../../cpp/cmake/thirdparty/get_dlpack.cmake) else() set(cuvs_FOUND OFF) endif() -include(rapids-cython) - if(NOT cuvs_FOUND) set(BUILD_TESTS OFF) + set(BUILD_C_LIBRARY ON) + + # Statically link dependencies if building wheels set(CUDA_STATIC_RUNTIME ON) + set(CUVS_USE_RAFT_STATIC ON) add_subdirectory(../../cpp cuvs-cpp EXCLUDE_FROM_ALL) - # When building the C++ libraries from source we must copy libcuvs.so alongside the Cython - # libraries TODO: when we have a single 'compiled' cuvs library, we shouldn't need this - set(cython_lib_dir cuvs_py) - install(TARGETS cuvs DESTINATION ${cython_lib_dir}) + set(cython_lib_dir cuvs) + install(TARGETS cuvs cuvs_c DESTINATION ${cython_lib_dir}) endif() rapids_cython_init() +add_subdirectory(cuvs/common) +add_subdirectory(cuvs/neighbors) + if(DEFINED cython_lib_dir) rapids_cython_add_rpath_entries(TARGET cuvs PATHS "${cython_lib_dir}") endif() diff --git a/python/cuvs/README.md b/python/cuvs/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/__init__.py b/python/cuvs/cuvs/__init__.py index 94b3a200b5..9f0481cb70 100644 --- a/python/cuvs/cuvs/__init__.py +++ b/python/cuvs/cuvs/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/python/cuvs/cuvs/common/CMakeLists.txt b/python/cuvs/cuvs/common/CMakeLists.txt new file mode 100644 index 0000000000..2cf1ff19c1 --- /dev/null +++ b/python/cuvs/cuvs/common/CMakeLists.txt @@ -0,0 +1,24 @@ +# ============================================================================= +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +# Set the list of Cython files to build +set(cython_sources cydlpack.pyx) +set(linked_libraries cuvs::cuvs cuvs_c) + +# Build all of the Cython targets +rapids_cython_create_modules( + CXX + SOURCE_FILES "${cython_sources}" + LINKED_LIBRARIES "${linked_libraries}" ASSOCIATED_TARGETS cuvs MODULE_PREFIX common_ +) diff --git a/python/cuvs/cuvs/common/__init__.pxd b/python/cuvs/cuvs/common/__init__.pxd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/common/__init__.py b/python/cuvs/cuvs/common/__init__.py new file mode 100644 index 0000000000..eb5666659f --- /dev/null +++ b/python/cuvs/cuvs/common/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from .temp_raft import auto_sync_resources + +__all__ = ["auto_sync_resources"] diff --git a/python/cuvs/cuvs/common/c_api.pxd b/python/cuvs/cuvs/common/c_api.pxd new file mode 100644 index 0000000000..6addbf16e2 --- /dev/null +++ b/python/cuvs/cuvs/common/c_api.pxd @@ -0,0 +1,32 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + + +from cuda.ccudart cimport cudaStream_t +from libc.stdint cimport uintptr_t + + +cdef extern from "cuvs/core/c_api.h": + ctypedef uintptr_t cuvsResources_t + + ctypedef enum cuvsError_t: + CUVS_ERROR, + CUVS_SUCCESS + + cuvsError_t cuvsResourcesCreate(cuvsResources_t* res) + cuvsError_t cuvsResourcesDestroy(cuvsResources_t res) + cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream) diff --git a/python/cuvs/cuvs/common/cydlpack.pxd b/python/cuvs/cuvs/common/cydlpack.pxd new file mode 100644 index 0000000000..73334e5007 --- /dev/null +++ b/python/cuvs/cuvs/common/cydlpack.pxd @@ -0,0 +1,70 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +from libc.stdint cimport int32_t, int64_t, uint8_t, uint16_t, uint64_t + + +cdef extern from "dlpack/dlpack.h" nogil: + ctypedef enum DLDeviceType: + kDLCPU + kDLCUDA + kDLCUDAHost + kDLOpenCL + kDLVulkan + kDLMetal + kDLVPI + kDLROCM + kDLROCMHost + kDLExtDev + kDLCUDAManaged + kDLOneAPI + kDLWebGPU + kDLHexagon + + ctypedef struct DLDevice: + DLDeviceType device_type + int32_t device_id + + ctypedef enum DLDataTypeCode: + kDLInt + kDLUInt + kDLFloat + kDLBfloat + kDLComplex + kDLBool + + ctypedef struct DLDataType: + uint8_t code + uint8_t bits + uint16_t lanes + + ctypedef struct DLTensor: + void* data + DLDevice device + int32_t ndim + DLDataType dtype + int64_t* shape + int64_t* strides + uint64_t byte_offset + + ctypedef struct DLManagedTensor: + DLTensor dl_tensor + void* manager_ctx + void (*deleter)(DLManagedTensor*) # noqa: E211 + + +cdef DLManagedTensor* dlpack_c(ary) diff --git a/python/cuvs/cuvs/common/cydlpack.pyx b/python/cuvs/cuvs/common/cydlpack.pyx new file mode 100644 index 0000000000..526f6c78e9 --- /dev/null +++ b/python/cuvs/cuvs/common/cydlpack.pyx @@ -0,0 +1,102 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +import numpy as np + +from libc cimport stdlib +from libc.stdint cimport uintptr_t + + +cdef void deleter(DLManagedTensor* tensor) noexcept: + if tensor.manager_ctx is NULL: + return + stdlib.free(tensor.dl_tensor.shape) + tensor.manager_ctx = NULL + stdlib.free(tensor) + + +cdef DLManagedTensor* dlpack_c(ary): + # todo(dgd): add checking options/parameters + cdef DLDeviceType dev_type + cdef DLDevice dev + cdef DLDataType dtype + cdef DLTensor tensor + cdef DLManagedTensor* dlm = \ + stdlib.malloc(sizeof(DLManagedTensor)) + + if ary.from_cai: + dev_type = DLDeviceType.kDLCUDA + else: + dev_type = DLDeviceType.kDLCPU + + dev.device_type = dev_type + dev.device_id = 0 + + # todo (dgd): change to nice dict + if ary.dtype == np.float32: + dtype.code = DLDataTypeCode.kDLFloat + dtype.bits = 32 + elif ary.dtype == np.float64: + dtype.code = DLDataTypeCode.kDLFloat + dtype.bits = 64 + elif ary.dtype == np.int8: + dtype.code = DLDataTypeCode.kDLInt + dtype.bits = 8 + elif ary.dtype == np.int32: + dtype.code = DLDataTypeCode.kDLInt + dtype.bits = 32 + elif ary.dtype == np.int64: + dtype.code = DLDataTypeCode.kDLInt + dtype.bits = 64 + elif ary.dtype == np.uint8: + dtype.code = DLDataTypeCode.kDLUInt + dtype.bits = 8 + elif ary.dtype == np.uint32: + dtype.code = DLDataTypeCode.kDLUInt + dtype.bits = 32 + elif ary.dtype == np.uint64: + dtype.code = DLDataTypeCode.kDLUInt + dtype.bits = 64 + elif ary.dtype == np.bool_: + dtype.code = DLDataTypeCode.kDLFloat + dtype.bits = 8 + + dtype.lanes = 1 + + cdef size_t ndim = len(ary.shape) + + cdef int64_t* shape = stdlib.malloc(ndim * sizeof(int64_t)) + + for i in range(ndim): + shape[i] = ary.shape[i] + + cdef uintptr_t tensor_ptr + tensor_ptr = ary.ai_["data"][0] + + tensor.data = tensor_ptr + tensor.device = dev + tensor.dtype = dtype + tensor.strides = NULL + tensor.ndim = ndim + tensor.shape = shape + tensor.byte_offset = 0 + + dlm.dl_tensor = tensor + dlm.manager_ctx = NULL + dlm.deleter = deleter + + return dlm diff --git a/python/cuvs/cuvs/common/temp_raft.py b/python/cuvs/cuvs/common/temp_raft.py new file mode 100644 index 0000000000..67944eeab7 --- /dev/null +++ b/python/cuvs/cuvs/common/temp_raft.py @@ -0,0 +1,55 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + + +# This file has code that will be upstreamed to RAFT + +import functools + +from pylibraft.common import DeviceResources + +_resources_param_string = """ + handle : Optional RAFT resource handle for reusing CUDA resources. + If a handle isn't supplied, CUDA resources will be + allocated inside this function and synchronized before the + function exits. If a handle is supplied, you will need to + explicitly synchronize yourself by calling `handle.sync()` + before accessing the output. +""".strip() + + +def auto_sync_resources(f): + """ + This is identical to auto_sync_handle except for the proposed name change. + """ + + @functools.wraps(f) + def wrapper(*args, resources=None, **kwargs): + sync_resources = resources is None + resources = resources if resources is not None else DeviceResources() + + ret_value = f(*args, resources=resources, **kwargs) + + if sync_resources: + resources.sync() + + return ret_value + + wrapper.__doc__ = wrapper.__doc__.format( + resources_docstring=_resources_param_string + ) + return wrapper diff --git a/python/cuvs/cuvs/neighbors/CMakeLists.txt b/python/cuvs/cuvs/neighbors/CMakeLists.txt new file mode 100644 index 0000000000..eaf418c60f --- /dev/null +++ b/python/cuvs/cuvs/neighbors/CMakeLists.txt @@ -0,0 +1,15 @@ +# ============================================================================= +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +add_subdirectory(cagra) diff --git a/python/cuvs/cuvs/neighbors/__init__.pxd b/python/cuvs/cuvs/neighbors/__init__.pxd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/neighbors/__init__.py b/python/cuvs/cuvs/neighbors/__init__.py new file mode 100644 index 0000000000..1f8f956d9c --- /dev/null +++ b/python/cuvs/cuvs/neighbors/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from cuvs.neighbors import cagra + +__all__ = ["common", "cagra"] diff --git a/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt b/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt new file mode 100644 index 0000000000..377cfe7793 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt @@ -0,0 +1,24 @@ +# ============================================================================= +# Copyright (c) 2023-2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +# Set the list of Cython files to build +set(cython_sources cagra.pyx) +set(linked_libraries cuvs::cuvs cuvs_c) + +# Build all of the Cython targets +rapids_cython_create_modules( + CXX + SOURCE_FILES "${cython_sources}" + LINKED_LIBRARIES "${linked_libraries}" ASSOCIATED_TARGETS cuvs MODULE_PREFIX neighbors_cagra_ +) diff --git a/python/cuvs/cuvs/neighbors/cagra/__init__.pxd b/python/cuvs/cuvs/neighbors/cagra/__init__.pxd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/neighbors/cagra/__init__.py b/python/cuvs/cuvs/neighbors/cagra/__init__.py new file mode 100644 index 0000000000..657c7d366e --- /dev/null +++ b/python/cuvs/cuvs/neighbors/cagra/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from .cagra import Index, IndexParams, SearchParams, build_index, search + +__all__ = ["Index", "IndexParams", "SearchParams", "build_index", "search"] diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pxd b/python/cuvs/cuvs/neighbors/cagra/cagra.pxd new file mode 100644 index 0000000000..4293bdc073 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pxd @@ -0,0 +1,95 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +from libc.stdint cimport ( + int8_t, + int64_t, + uint8_t, + uint32_t, + uint64_t, + uintptr_t, +) + +from cuvs.common.c_api cimport cuvsError_t, cuvsResources_t +from cuvs.common.cydlpack cimport DLDataType, DLManagedTensor + + +cdef extern from "cuvs/neighbors/cagra.h" nogil: + + ctypedef enum cuvsCagraGraphBuildAlgo: + IVF_PQ + NN_DESCENT + + ctypedef struct cuvsCagraIndexParams: + size_t intermediate_graph_degree + size_t graph_degree + cuvsCagraGraphBuildAlgo build_algo + size_t nn_descent_niter + + ctypedef cuvsCagraIndexParams* cuvsCagraIndexParams_t + + ctypedef enum cuvsCagraSearchAlgo: + SINGLE_CTA, + MULTI_CTA, + MULTI_KERNEL, + AUTO + + ctypedef enum cuvsCagraHashMode: + HASH, + SMALL, + AUTO_HASH + + ctypedef struct cuvsCagraSearchParams: + size_t max_queries + size_t itopk_size + size_t max_iterations + cuvsCagraSearchAlgo algo + size_t team_size + size_t search_width + size_t min_iterations + size_t thread_block_size + cuvsCagraHashMode hashmap_mode + size_t hashmap_min_bitlen + float hashmap_max_fill_rate + uint32_t num_random_samplings + uint64_t rand_xor_mask + + ctypedef struct cuvsCagraIndex: + uintptr_t addr + DLDataType dtype + + ctypedef cuvsCagraIndex* cuvsCagraIndex_t + + cuvsError_t cuvsCagraIndexParamsCreate(cuvsCagraIndexParams_t* params) + + cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t index) + + cuvsError_t cuvsCagraIndexCreate(cuvsCagraIndex_t* index) + + cuvsError_t cuvsCagraIndexDestroy(cuvsCagraIndex_t index) + + cuvsError_t cuvsCagraBuild(cuvsResources_t res, + cuvsCagraIndexParams* params, + DLManagedTensor* dataset, + cuvsCagraIndex_t index) except + + + cuvsError_t cuvsCagraSearch(cuvsResources_t res, + cuvsCagraSearchParams* params, + cuvsCagraIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances) except + diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx new file mode 100644 index 0000000000..bf17fe6a55 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx @@ -0,0 +1,502 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +import numpy as np + +cimport cuvs.common.cydlpack + +from cuvs.common.temp_raft import auto_sync_resources + +from cython.operator cimport dereference as deref +from libcpp cimport bool, cast + +from cuvs.common cimport cydlpack + +from pylibraft.common import ( + DeviceResources, + Stream, + auto_convert_output, + cai_wrapper, + device_ndarray, +) +from pylibraft.common.cai_wrapper import wrap_array +from pylibraft.common.interruptible import cuda_interruptible +from pylibraft.neighbors.common import _check_input_array + +from libc.stdint cimport ( + int8_t, + int64_t, + uint8_t, + uint32_t, + uint64_t, + uintptr_t, +) + +from cuvs.common.c_api cimport ( + cuvsError_t, + cuvsResources_t, + cuvsResourcesCreate, +) + + +cdef class IndexParams: + """ + Parameters to build index for CAGRA nearest neighbor search + + Parameters + ---------- + metric : string denoting the metric type, default="sqeuclidean" + Valid values for metric: ["sqeuclidean"], where + - sqeuclidean is the euclidean distance without the square root + operation, i.e.: distance(a,b) = \\sum_i (a_i - b_i)^2 + intermediate_graph_degree : int, default = 128 + + graph_degree : int, default = 64 + + build_algo: string denoting the graph building algorithm to use, \ + default = "ivf_pq" + Valid values for algo: ["ivf_pq", "nn_descent"], where + - ivf_pq will use the IVF-PQ algorithm for building the knn graph + - nn_descent (experimental) will use the NN-Descent algorithm for + building the knn graph. It is expected to be generally + faster than ivf_pq. + """ + cdef cuvsCagraIndexParams* params + + def __init__(self, *, + metric="sqeuclidean", + intermediate_graph_degree=128, + graph_degree=64, + build_algo="ivf_pq", + nn_descent_niter=20): + + cuvsCagraIndexParamsCreate(&self.params) + + # todo (dgd): enable once other metrics are present + # and exposed in cuVS C API + # self.params.metric = _get_metric(metric) + # self.params.metric_arg = 0 + self.params.intermediate_graph_degree = intermediate_graph_degree + self.params.graph_degree = graph_degree + if build_algo == "ivf_pq": + self.params.build_algo = cuvsCagraGraphBuildAlgo.IVF_PQ + elif build_algo == "nn_descent": + self.params.build_algo = cuvsCagraGraphBuildAlgo.NN_DESCENT + self.params.nn_descent_niter = nn_descent_niter + + # @property + # def metric(self): + # return self.params.metric + + @property + def intermediate_graph_degree(self): + return self.params.intermediate_graph_degree + + @property + def graph_degree(self): + return self.params.graph_degree + + @property + def build_algo(self): + return self.params.build_algo + + @property + def nn_descent_niter(self): + return self.params.nn_descent_niter + + +cdef class Index: + cdef cuvsCagraIndex_t index + cdef bool trained + + def __cinit__(self): + cdef cuvsError_t index_create_status + index_create_status = cuvsCagraIndexCreate(&self.index) + self.trained = False + + if index_create_status == cuvsError_t.CUVS_ERROR: + raise RuntimeError("Failed to create index.") + + def __dealloc__(self): + cdef cuvsError_t index_destroy_status + if self.index is not NULL: + index_destroy_status = cuvsCagraIndexDestroy(self.index) + if index_destroy_status == cuvsError_t.CUVS_ERROR: + raise Exception("Failed to deallocate index.") + + @property + def trained(self): + return self.trained + + def __repr__(self): + # todo(dgd): update repr as we expose data through C API + attr_str = [] + return "Index(type=CAGRA, metric=L2" + (", ".join(attr_str)) + ")" + + +@auto_sync_resources +def build_index(IndexParams index_params, dataset, resources=None): + """ + Build the CAGRA index from the dataset for efficient search. + + The build performs two different steps- first an intermediate knn-graph is + constructed, then it's optimized it to create the final graph. The + index_params object controls the node degree of these graphs. + + It is required that both the dataset and the optimized graph fit the + GPU memory. + + The following distance metrics are supported: + - L2 + + Parameters + ---------- + index_params : IndexParams object + dataset : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + {resources_docstring} + + Returns + ------- + index: cuvs.cagra.Index + + Examples + -------- + + >>> import cupy as cp + >>> from cuvs.neighbors import cagra + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> k = 10 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> build_params = cagra.IndexParams(metric="sqeuclidean") + >>> index = cagra.build_index(build_params, dataset) + >>> distances, neighbors = cagra.search(cagra.SearchParams(), + ... index, dataset, + ... k) + >>> distances = cp.asarray(distances) + >>> neighbors = cp.asarray(neighbors) + """ + + # todo(dgd): we can make the check of dtype a parameter of wrap_array + # in RAFT to make this a single call + dataset_ai = wrap_array(dataset) + _check_input_array(dataset_ai, [np.dtype('float32'), np.dtype('byte'), + np.dtype('ubyte')]) + + cdef cuvsResources_t res_ + cdef cuvsError_t cstat + + cstat = cuvsResourcesCreate(&res_) + if cstat == cuvsError_t.CUVS_ERROR: + raise RuntimeError("Error creating Device Reources.") + + cdef Index idx = Index() + cdef cuvsError_t build_status + cdef cydlpack.DLManagedTensor* dataset_dlpack = \ + cydlpack.dlpack_c(dataset_ai) + cdef cuvsCagraIndexParams* params = index_params.params + + with cuda_interruptible(): + build_status = cuvsCagraBuild( + res_, + params, + dataset_dlpack, + idx.index + ) + + if build_status == cuvsError_t.CUVS_ERROR: + raise RuntimeError("Index failed to build.") + else: + idx.trained = True + + return idx + + +cdef class SearchParams: + """ + CAGRA search parameters + + Parameters + ---------- + max_queries: int, default = 0 + Maximum number of queries to search at the same time (batch size). + Auto select when 0. + itopk_size: int, default = 64 + Number of intermediate search results retained during the search. + This is the main knob to adjust trade off between accuracy and + search speed. Higher values improve the search accuracy. + max_iterations: int, default = 0 + Upper limit of search iterations. Auto select when 0. + algo: string denoting the search algorithm to use, default = "auto" + Valid values for algo: ["auto", "single_cta", "multi_cta"], where + - auto will automatically select the best value based on query size + - single_cta is better when query contains larger number of + vectors (e.g >10) + - multi_cta is better when query contains only a few vectors + team_size: int, default = 0 + Number of threads used to calculate a single distance. 4, 8, 16, + or 32. + search_width: int, default = 1 + Number of graph nodes to select as the starting point for the + search in each iteration. + min_iterations: int, default = 0 + Lower limit of search iterations. + thread_block_size: int, default = 0 + Thread block size. 0, 64, 128, 256, 512, 1024. + Auto selection when 0. + hashmap_mode: string denoting the type of hash map to use. + It's usually better to allow the algorithm to select this value, + default = "auto". + Valid values for hashmap_mode: ["auto", "small", "hash"], where + - auto will automatically select the best value based on algo + - small will use the small shared memory hash table with resetting. + - hash will use a single hash table in global memory. + hashmap_min_bitlen: int, default = 0 + Upper limit of hashmap fill rate. More than 0.1, less than 0.9. + hashmap_max_fill_rate: float, default = 0.5 + Upper limit of hashmap fill rate. More than 0.1, less than 0.9. + num_random_samplings: int, default = 1 + Number of iterations of initial random seed node selection. 1 or + more. + rand_xor_mask: int, default = 0x128394 + Bit mask used for initial random seed node selection. + """ + cdef cuvsCagraSearchParams params + + def __init__(self, *, + max_queries=0, + itopk_size=64, + max_iterations=0, + algo="auto", + team_size=0, + search_width=1, + min_iterations=0, + thread_block_size=0, + hashmap_mode="auto", + hashmap_min_bitlen=0, + hashmap_max_fill_rate=0.5, + num_random_samplings=1, + rand_xor_mask=0x128394): + self.params.max_queries = max_queries + self.params.itopk_size = itopk_size + self.params.max_iterations = max_iterations + if algo == "single_cta": + self.params.algo = cuvsCagraSearchAlgo.SINGLE_CTA + elif algo == "multi_cta": + self.params.algo = cuvsCagraSearchAlgo.MULTI_CTA + elif algo == "multi_kernel": + self.params.algo = cuvsCagraSearchAlgo.MULTI_KERNEL + elif algo == "auto": + self.params.algo = cuvsCagraSearchAlgo.AUTO + else: + raise ValueError("`algo` value not supported.") + + self.params.team_size = team_size + self.params.search_width = search_width + self.params.min_iterations = min_iterations + self.params.thread_block_size = thread_block_size + if hashmap_mode == "hash": + self.params.hashmap_mode = cuvsCagraHashMode.HASH + elif hashmap_mode == "small": + self.params.hashmap_mode = cuvsCagraHashMode.SMALL + elif hashmap_mode == "auto": + self.params.hashmap_mode = cuvsCagraHashMode.AUTO_HASH + else: + raise ValueError("`hashmap_mode` value not supported.") + + self.params.hashmap_min_bitlen = hashmap_min_bitlen + self.params.hashmap_max_fill_rate = hashmap_max_fill_rate + self.params.num_random_samplings = num_random_samplings + self.params.rand_xor_mask = rand_xor_mask + + def __repr__(self): + attr_str = [attr + "=" + str(getattr(self, attr)) + for attr in [ + "max_queries", "itopk_size", "max_iterations", "algo", + "team_size", "search_width", "min_iterations", + "thread_block_size", "hashmap_mode", + "hashmap_min_bitlen", "hashmap_max_fill_rate", + "num_random_samplings", "rand_xor_mask"]] + return "SearchParams(type=CAGRA, " + (", ".join(attr_str)) + ")" + + @property + def max_queries(self): + return self.params.max_queries + + @property + def itopk_size(self): + return self.params.itopk_size + + @property + def max_iterations(self): + return self.params.max_iterations + + @property + def algo(self): + return self.params.algo + + @property + def team_size(self): + return self.params.team_size + + @property + def search_width(self): + return self.params.search_width + + @property + def min_iterations(self): + return self.params.min_iterations + + @property + def thread_block_size(self): + return self.params.thread_block_size + + @property + def hashmap_mode(self): + return self.params.hashmap_mode + + @property + def hashmap_min_bitlen(self): + return self.params.hashmap_min_bitlen + + @property + def hashmap_max_fill_rate(self): + return self.params.hashmap_max_fill_rate + + @property + def num_random_samplings(self): + return self.params.num_random_samplings + + @property + def rand_xor_mask(self): + return self.params.rand_xor_mask + + +@auto_sync_resources +@auto_convert_output +def search(SearchParams search_params, + Index index, + queries, + k, + neighbors=None, + distances=None, + resources=None): + """ + Find the k nearest neighbors for each query. + + Parameters + ---------- + search_params : SearchParams + index : Index + Trained CAGRA index. + queries : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + k : int + The number of neighbors. + neighbors : Optional CUDA array interface compliant matrix shape + (n_queries, k), dtype int64_t. If supplied, neighbor + indices will be written here in-place. (default None) + distances : Optional CUDA array interface compliant matrix shape + (n_queries, k) If supplied, the distances to the + neighbors will be written here in-place. (default None) + {resources_docstring} + + Examples + -------- + >>> import cupy as cp + >>> from cuvs.neighbors import cagra + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> # Build index + >>> index = cagra.build_index(cagra.IndexParams(), dataset) + >>> # Search using the built index + >>> queries = cp.random.random_sample((n_queries, n_features), + ... dtype=cp.float32) + >>> k = 10 + >>> search_params = cagra.SearchParams( + ... max_queries=100, + ... itopk_size=64 + ... ) + >>> # Using a pooling allocator reduces overhead of temporary array + >>> # creation during search. This is useful if multiple searches + >>> # are performad with same query size. + >>> distances, neighbors = cagra.search(search_params, index, queries, + ... k) + >>> neighbors = cp.asarray(neighbors) + >>> distances = cp.asarray(distances) + """ + if not index.trained: + raise ValueError("Index needs to be built before calling search.") + + cdef cuvsResources_t res_ + cdef cuvsError_t cstat + + cstat = cuvsResourcesCreate(&res_) + if cstat == cuvsError_t.CUVS_ERROR: + raise RuntimeError("Error creating Device Reources.") + + # todo(dgd): we can make the check of dtype a parameter of wrap_array + # in RAFT to make this a single call + queries_cai = wrap_array(queries) + _check_input_array(queries_cai, [np.dtype('float32'), np.dtype('byte'), + np.dtype('ubyte')]) + + cdef uint32_t n_queries = queries_cai.shape[0] + + if neighbors is None: + neighbors = device_ndarray.empty((n_queries, k), dtype='uint32') + + neighbors_cai = wrap_array(neighbors) + _check_input_array(neighbors_cai, [np.dtype('uint32')], + exp_rows=n_queries, exp_cols=k) + + if distances is None: + distances = device_ndarray.empty((n_queries, k), dtype='float32') + + distances_cai = wrap_array(distances) + _check_input_array(distances_cai, [np.dtype('float32')], + exp_rows=n_queries, exp_cols=k) + + cdef cuvsCagraSearchParams* params = &search_params.params + cdef cuvsError_t search_status + cdef cydlpack.DLManagedTensor* queries_dlpack = \ + cydlpack.dlpack_c(queries_cai) + cdef cydlpack.DLManagedTensor* neighbors_dlpack = \ + cydlpack.dlpack_c(neighbors_cai) + cdef cydlpack.DLManagedTensor* distances_dlpack = \ + cydlpack.dlpack_c(distances_cai) + + with cuda_interruptible(): + search_status = cuvsCagraSearch( + res_, + params, + index.index, + queries_dlpack, + neighbors_dlpack, + distances_dlpack + ) + + if search_status == cuvsError_t.CUVS_ERROR: + raise RuntimeError("Search failed.") + + return (distances, neighbors) diff --git a/python/cuvs/cuvs/test/__init__py b/python/cuvs/cuvs/test/__init__py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/test/ann_utils.py b/python/cuvs/cuvs/test/ann_utils.py new file mode 100644 index 0000000000..60db7f3273 --- /dev/null +++ b/python/cuvs/cuvs/test/ann_utils.py @@ -0,0 +1,35 @@ +# Copyright (c) 2023-2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# h ttp://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + + +def generate_data(shape, dtype): + if dtype == np.byte: + x = np.random.randint(-127, 128, size=shape, dtype=np.byte) + elif dtype == np.ubyte: + x = np.random.randint(0, 255, size=shape, dtype=np.ubyte) + else: + x = np.random.random_sample(shape).astype(dtype) + + return x + + +def calc_recall(ann_idx, true_nn_idx): + assert ann_idx.shape == true_nn_idx.shape + n = 0 + for i in range(ann_idx.shape[0]): + n += np.intersect1d(ann_idx[i, :], true_nn_idx[i, :]).size + recall = n / ann_idx.size + return recall diff --git a/python/cuvs/cuvs/test/test_cagra.py b/python/cuvs/cuvs/test/test_cagra.py new file mode 100644 index 0000000000..6074eee3a7 --- /dev/null +++ b/python/cuvs/cuvs/test/test_cagra.py @@ -0,0 +1,175 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# h ttp://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from pylibraft.common import device_ndarray +from sklearn.neighbors import NearestNeighbors +from sklearn.preprocessing import normalize + +from cuvs.neighbors import cagra +from cuvs.test.ann_utils import calc_recall, generate_data + + +def run_cagra_build_search_test( + n_rows=10000, + n_cols=10, + n_queries=100, + k=10, + dtype=np.float32, + metric="euclidean", + intermediate_graph_degree=128, + graph_degree=64, + build_algo="ivf_pq", + array_type="device", + compare=True, + inplace=True, + add_data_on_build=True, + search_params={}, +): + dataset = generate_data((n_rows, n_cols), dtype) + if metric == "inner_product": + dataset = normalize(dataset, norm="l2", axis=1) + dataset_device = device_ndarray(dataset) + + build_params = cagra.IndexParams( + metric=metric, + intermediate_graph_degree=intermediate_graph_degree, + graph_degree=graph_degree, + build_algo=build_algo, + ) + + if array_type == "device": + index = cagra.build_index(build_params, dataset_device) + else: + index = cagra.build_index(build_params, dataset) + + if not add_data_on_build: + dataset_1 = dataset[: n_rows // 2, :] + dataset_2 = dataset[n_rows // 2 :, :] + indices_1 = np.arange(n_rows // 2, dtype=np.uint32) + indices_2 = np.arange(n_rows // 2, n_rows, dtype=np.uint32) + if array_type == "device": + dataset_1_device = device_ndarray(dataset_1) + dataset_2_device = device_ndarray(dataset_2) + indices_1_device = device_ndarray(indices_1) + indices_2_device = device_ndarray(indices_2) + index = cagra.extend(index, dataset_1_device, indices_1_device) + index = cagra.extend(index, dataset_2_device, indices_2_device) + else: + index = cagra.extend(index, dataset_1, indices_1) + index = cagra.extend(index, dataset_2, indices_2) + + queries = generate_data((n_queries, n_cols), dtype) + out_idx = np.zeros((n_queries, k), dtype=np.uint32) + out_dist = np.zeros((n_queries, k), dtype=np.float32) + + queries_device = device_ndarray(queries) + out_idx_device = device_ndarray(out_idx) if inplace else None + out_dist_device = device_ndarray(out_dist) if inplace else None + + search_params = cagra.SearchParams(**search_params) + + ret_output = cagra.search( + search_params, + index, + queries_device, + k, + neighbors=out_idx_device, + distances=out_dist_device, + ) + + if not inplace: + out_dist_device, out_idx_device = ret_output + + if not compare: + return + + out_idx = out_idx_device.copy_to_host() + out_dist = out_dist_device.copy_to_host() + + # Calculate reference values with sklearn + skl_metric = { + "sqeuclidean": "sqeuclidean", + "inner_product": "cosine", + "euclidean": "euclidean", + }[metric] + nn_skl = NearestNeighbors( + n_neighbors=k, algorithm="brute", metric=skl_metric + ) + nn_skl.fit(dataset) + skl_idx = nn_skl.kneighbors(queries, return_distance=False) + + recall = calc_recall(out_idx, skl_idx) + assert recall > 0.7 + + +@pytest.mark.parametrize("inplace", [True, False]) +@pytest.mark.parametrize("dtype", [np.float32, np.int8, np.uint8]) +@pytest.mark.parametrize("array_type", ["device", "host"]) +@pytest.mark.parametrize("build_algo", ["ivf_pq", "nn_descent"]) +def test_cagra_dataset_dtype_host_device( + dtype, array_type, inplace, build_algo +): + # Note that inner_product tests use normalized input which we cannot + # represent in int8, therefore we test only sqeuclidean metric here. + run_cagra_build_search_test( + dtype=dtype, + inplace=inplace, + array_type=array_type, + build_algo=build_algo, + ) + + +@pytest.mark.parametrize( + "params", + [ + { + "intermediate_graph_degree": 64, + "graph_degree": 32, + "add_data_on_build": True, + "k": 1, + "metric": "euclidean", + "build_algo": "ivf_pq", + }, + { + "intermediate_graph_degree": 32, + "graph_degree": 16, + "add_data_on_build": False, + "k": 5, + "metric": "sqeuclidean", + "build_algo": "ivf_pq", + }, + { + "intermediate_graph_degree": 128, + "graph_degree": 32, + "add_data_on_build": True, + "k": 10, + "metric": "inner_product", + "build_algo": "nn_descent", + }, + ], +) +def test_cagra_index_params(params): + # Note that inner_product tests use normalized input which we cannot + # represent in int8, therefore we test only sqeuclidean metric here. + run_cagra_build_search_test( + k=params["k"], + metric=params["metric"], + graph_degree=params["graph_degree"], + intermediate_graph_degree=params["intermediate_graph_degree"], + compare=False, + build_algo=params["build_algo"], + ) diff --git a/python/cuvs/cuvs/test/test_doctests.py b/python/cuvs/cuvs/test/test_doctests.py new file mode 100644 index 0000000000..6d56ffaa2f --- /dev/null +++ b/python/cuvs/cuvs/test/test_doctests.py @@ -0,0 +1,116 @@ +# +# Copyright (c) 2022-2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import contextlib +import doctest +import inspect +import io + +import pytest + +import cuvs.neighbors + +# Code adapted from https://github.com/rapidsai/cudf/blob/branch-23.02/python/cudf/cudf/tests/test_doctests.py # noqa + + +def _name_in_all(parent, name): + return name in getattr(parent, "__all__", []) + + +def _is_public_name(parent, name): + return not name.startswith("_") + + +def _find_doctests_in_obj(obj, finder=None, criteria=None): + """Find all doctests in an object. + + Parameters + ---------- + obj : module or class + The object to search for docstring examples. + finder : doctest.DocTestFinder, optional + The DocTestFinder object to use. If not provided, a DocTestFinder is + constructed. + criteria : callable, optional + Callable indicating whether to recurse over members of the provided + object. If not provided, names not defined in the object's ``__all__`` + property are ignored. + + Yields + ------ + doctest.DocTest + The next doctest found in the object. + """ + if finder is None: + finder = doctest.DocTestFinder() + if criteria is None: + criteria = _name_in_all + for docstring in finder.find(obj): + if docstring.examples: + yield docstring + for name, member in inspect.getmembers(obj): + # Only recurse over members matching the criteria + if not criteria(obj, name): + continue + # Recurse over the public API of modules (objects defined in the + # module's __all__) + if inspect.ismodule(member): + yield from _find_doctests_in_obj( + member, finder, criteria=_name_in_all + ) + # Recurse over the public API of classes (attributes not prefixed with + # an underscore) + if inspect.isclass(member): + yield from _find_doctests_in_obj( + member, finder, criteria=_is_public_name + ) + + # doctest finder seems to dislike cython functions, since + # `inspect.isfunction` doesn't return true for them. hack around this + if callable(member) and not inspect.isfunction(member): + for docstring in finder.find(member): + if docstring.examples: + yield docstring + + +# since the root pylibraft module doesn't import submodules (or define an +# __all__) we are explicitly adding all the submodules we want to run +# doctests for here +DOC_STRINGS = list(_find_doctests_in_obj(cuvs.neighbors)) +DOC_STRINGS.extend(_find_doctests_in_obj(cuvs.neighbors.cagra)) + + +@pytest.mark.parametrize( + "docstring", + DOC_STRINGS, + ids=lambda docstring: docstring.name, +) +def test_docstring(docstring): + # We ignore differences in whitespace in the doctest output, and enable + # the use of an ellipsis "..." to match any string in the doctest + # output. An ellipsis is useful for, e.g., memory addresses or + # imprecise floating point values. + optionflags = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE + runner = doctest.DocTestRunner(optionflags=optionflags) + + # Capture stdout and include failing outputs in the traceback. + doctest_stdout = io.StringIO() + with contextlib.redirect_stdout(doctest_stdout): + runner.run(docstring) + results = runner.summarize() + assert not results.failed, ( + f"{results.failed} of {results.attempted} doctests failed for " + f"{docstring.name}:\n{doctest_stdout.getvalue()}" + ) diff --git a/python/cuvs/pyproject.toml b/python/cuvs/pyproject.toml index cba8d4adf0..664cf29692 100644 --- a/python/cuvs/pyproject.toml +++ b/python/cuvs/pyproject.toml @@ -1,4 +1,4 @@ -# Copyright (c) 2022, NVIDIA CORPORATION. +# Copyright (c) 2024, NVIDIA CORPORATION. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,12 +19,11 @@ requires = [ "cuda-python>=11.7.1,<12.0a0", "cython>=3.0.0", "ninja", - "rmm==24.2.*", - "scikit-build>=0.13.1", - "setuptools", - "wheel", + "pylibraft==24.4.*", + "rmm==24.4.*", + "scikit-build-core[pyproject]>=0.7.0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. -build-backend = "setuptools.build_meta" +build-backend = "scikit_build_core.build" [project] name = "cuvs" @@ -38,8 +37,8 @@ license = { text = "Apache 2.0" } requires-python = ">=3.9" dependencies = [ "cuda-python>=11.7.1,<12.0a0", - "numpy>=1.21", - "rmm==24.2.*", + "numpy>=1.23", + "rmm==24.4.*", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ "Intended Audience :: Developers", @@ -51,10 +50,10 @@ classifiers = [ [project.optional-dependencies] test = [ "cupy-cuda11x>=12.0.0", - "pytest", + "pylibraft==24.4.*", "pytest-cov", + "pytest==7.*", "scikit-learn", - "scipy", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. [project.urls] @@ -109,3 +108,16 @@ skip = [ "dist", "__init__.py", ] + +[tool.scikit-build] +build-dir = "build/{wheel_tag}" +cmake.build-type = "Release" +cmake.minimum-version = "3.26.4" +ninja.make-fallback = true +sdist.reproducible = true +wheel.packages = ["cuvs"] + +[tool.scikit-build.metadata.version] +provider = "scikit_build_core.metadata.regex" +input = "cuvs/VERSION" +regex = "(?P.*)" diff --git a/python/cuvs/setup.cfg b/python/cuvs/setup.cfg index 3574b44160..57b4954bc6 100644 --- a/python/cuvs/setup.cfg +++ b/python/cuvs/setup.cfg @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2024, NVIDIA CORPORATION. [isort] line_length=79 @@ -12,6 +12,7 @@ known_dask= distributed dask_cuda known_rapids= + cuvs nvtext cudf cuml diff --git a/python/cuvs/setup.py b/python/cuvs/setup.py deleted file mode 100644 index 4e825dab29..0000000000 --- a/python/cuvs/setup.py +++ /dev/null @@ -1,37 +0,0 @@ -# -# Copyright (c) 2022-2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from setuptools import find_packages -from skbuild import setup - - -def exclude_libcxx_symlink(cmake_manifest): - return list( - filter( - lambda name: not ("include/rapids/libcxx/include" in name), - cmake_manifest, - ) - ) - - -packages = find_packages(include=["cuvs*"]) -setup( - # Don't want libcxx getting pulled into wheel builds. - cmake_process_manifest_hook=exclude_libcxx_symlink, - packages=packages, - package_data={key: ["VERSION", "*.pxd"] for key in packages}, - zip_safe=False, -) From 8e6979f5548e447e155af6d728dc7c29a6e5af79 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Mon, 4 Mar 2024 11:30:10 -0800 Subject: [PATCH 004/623] Add Rust bindings for CAGRA (#34) --- .github/workflows/build.yaml | 13 ++ .github/workflows/pr.yaml | 11 ++ README.md | 61 +++++++++ ci/build_rust.sh | 40 ++++++ dependencies.yaml | 16 ++- rust/Cargo.toml | 16 +++ rust/cuvs-sys/Cargo.toml | 16 +++ rust/cuvs-sys/build.rs | 112 ++++++++++++++++ rust/cuvs-sys/cuvs_c_wrapper.h | 20 +++ rust/cuvs-sys/src/lib.rs | 54 ++++++++ rust/cuvs/Cargo.toml | 16 +++ rust/cuvs/build.rs | 29 ++++ rust/cuvs/examples/cagra.rs | 78 +++++++++++ rust/cuvs/src/cagra/index.rs | 141 ++++++++++++++++++++ rust/cuvs/src/cagra/index_params.rs | 110 ++++++++++++++++ rust/cuvs/src/cagra/mod.rs | 23 ++++ rust/cuvs/src/cagra/search_params.rs | 168 +++++++++++++++++++++++ rust/cuvs/src/dlpack.rs | 190 +++++++++++++++++++++++++++ rust/cuvs/src/error.rs | 51 +++++++ rust/cuvs/src/lib.rs | 24 ++++ rust/cuvs/src/resources.rs | 52 ++++++++ 21 files changed, 1240 insertions(+), 1 deletion(-) create mode 100755 ci/build_rust.sh create mode 100644 rust/Cargo.toml create mode 100644 rust/cuvs-sys/Cargo.toml create mode 100644 rust/cuvs-sys/build.rs create mode 100644 rust/cuvs-sys/cuvs_c_wrapper.h create mode 100644 rust/cuvs-sys/src/lib.rs create mode 100644 rust/cuvs/Cargo.toml create mode 100644 rust/cuvs/build.rs create mode 100644 rust/cuvs/examples/cagra.rs create mode 100644 rust/cuvs/src/cagra/index.rs create mode 100644 rust/cuvs/src/cagra/index_params.rs create mode 100644 rust/cuvs/src/cagra/mod.rs create mode 100644 rust/cuvs/src/cagra/search_params.rs create mode 100644 rust/cuvs/src/dlpack.rs create mode 100644 rust/cuvs/src/error.rs create mode 100644 rust/cuvs/src/lib.rs create mode 100644 rust/cuvs/src/resources.rs diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 34cf1f5b07..61ebdf0bca 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -34,6 +34,19 @@ jobs: branch: ${{ inputs.branch }} date: ${{ inputs.date }} sha: ${{ inputs.sha }} + rust-build: + needs: cpp-build + secrets: inherit + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + with: + build_type: ${{ inputs.build_type || 'branch' }} + branch: ${{ inputs.branch }} + arch: "amd64" + date: ${{ inputs.date }} + container_image: "rapidsai/ci-conda:latest" + node_type: "gpu-v100-latest-1" + run_script: "ci/build_rust.sh" + sha: ${{ inputs.sha }} python-build: needs: [cpp-build] secrets: inherit diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 5799f51087..7c6db2c603 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -19,6 +19,7 @@ jobs: - conda-python-build - conda-python-tests - docs-build + - rust-build - wheel-build-cuvs - wheel-tests-cuvs - devcontainer @@ -72,6 +73,16 @@ jobs: arch: "amd64" container_image: "rapidsai/ci-conda:latest" run_script: "ci/build_docs.sh" + rust-build: + needs: conda-cpp-build + secrets: inherit + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + with: + build_type: pull-request + node_type: "gpu-v100-latest-1" + arch: "amd64" + container_image: "rapidsai/ci-conda:latest" + run_script: "ci/build_rust.sh" wheel-build-cuvs: needs: checks secrets: inherit diff --git a/README.md b/README.md index dfba9eb4aa..14bb028123 100755 --- a/README.md +++ b/README.md @@ -106,6 +106,67 @@ cuvsCagraIndexParamsDestroy(index_params); cuvsResourcesDestroy(res); ``` +### Rust API + +```rust +use cuvs::cagra::{Index, IndexParams, SearchParams}; +use cuvs::{ManagedTensor, Resources, Result}; + +use ndarray::s; +use ndarray_rand::rand_distr::Uniform; +use ndarray_rand::RandomExt; + +/// Example showing how to index and search data with CAGRA +fn cagra_example() -> Result<()> { + let res = Resources::new()?; + + // Create a new random dataset to index + let n_datapoints = 65536; + let n_features = 512; + let dataset = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + // build the cagra index + let build_params = IndexParams::new()?; + let index = Index::build(&res, &build_params, &dataset)?; + println!( + "Indexed {}x{} datapoints into cagra index", + n_datapoints, n_features + ); + + // use the first 4 points from the dataset as queries : will test that we get them back + // as their own nearest neighbor + let n_queries = 4; + let queries = dataset.slice(s![0..n_queries, ..]); + + let k = 10; + + // CAGRA search API requires queries and outputs to be on device memory + // copy query data over, and allocate new device memory for the distances/ neighbors + // outputs + let queries = ManagedTensor::from(&queries).to_device(&res)?; + let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); + let neighbors = ManagedTensor::from(&neighbors_host).to_device(&res)?; + + let mut distances_host = ndarray::Array::::zeros((n_queries, k)); + let distances = ManagedTensor::from(&distances_host).to_device(&res)?; + + let search_params = SearchParams::new()?; + + index.search(&res, &search_params, &queries, &neighbors, &distances)?; + + // Copy back to host memory + distances.to_host(&res, &mut distances_host)?; + neighbors.to_host(&res, &mut neighbors_host)?; + + // nearest neighbors should be themselves, since queries are from the + // dataset + println!("Neighbors {:?}", neighbors_host); + println!("Distances {:?}", distances_host); + Ok(()) +} +``` + ## Contributing diff --git a/ci/build_rust.sh b/ci/build_rust.sh new file mode 100755 index 0000000000..895dd41e07 --- /dev/null +++ b/ci/build_rust.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Copyright (c) 2024, NVIDIA CORPORATION. + +set -euo pipefail + +rapids-logger "Create test conda environment" +. /opt/conda/etc/profile.d/conda.sh + +rapids-dependency-file-generator \ + --output conda \ + --file_key rust \ + --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" | tee env.yaml + +rapids-mamba-retry env create --force -f env.yaml -n rust + +# seeing failures on activating the environment here on unbound locals +# apply workaround from https://github.com/conda/conda/issues/8186#issuecomment-532874667 +set +eu +conda activate rust +set -eu + +rapids-print-env + +# we need to set up LIBCLANG_PATH to allow rust bindgen to work, +# grab it from the conda env +export LIBCLANG_PATH=$(dirname $(find /opt/conda -name libclang.so | head -n 1)) +echo "LIBCLANG_PATH=$LIBCLANG_PATH" + +rapids-logger "Downloading artifacts from previous jobs" +CPP_CHANNEL=$(rapids-download-conda-from-s3 cpp) + +# installing libcuvs/libraft will speed up the rust build substantially +rapids-mamba-retry install \ + --channel "${CPP_CHANNEL}" \ + libcuvs \ + libraft + +# build and test the rust bindings +cd rust +cargo test diff --git a/dependencies.yaml b/dependencies.yaml index f17b84dffa..d7562ce573 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -44,7 +44,12 @@ files: - cupy - docs - py_version - - test_py_cuvs + rust: + output: none + includes: + - build + - cuda + - rust py_build_py_cuvs: output: pyproject pyproject_dir: python/cuvs @@ -308,6 +313,15 @@ dependencies: - recommonmark - sphinx-copybutton - sphinx-markdown-tables + rust: + common: + - output_types: [conda] + packages: + - make + - rust + # clang/liblclang only needed for bindgen support + - clang + - libclang build_wheels: common: - output_types: [requirements, pyproject] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000000..7e9bfe4ecb --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +members = [ + "cuvs", + "cuvs-sys", +] +resolver = "2" + +[workspace.package] +version = "0.1.0" +edition = "2021" +repository = "https://github.com/rapidsai/cuvs" +homepage = "https://github.com/rapidsai/cuvs" +description = "RAPIDS vector search library" +authors = ["NVIDIA Corporation"] +license = "Apache-2.0" + diff --git a/rust/cuvs-sys/Cargo.toml b/rust/cuvs-sys/Cargo.toml new file mode 100644 index 0000000000..b011e6b374 --- /dev/null +++ b/rust/cuvs-sys/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cuvs-sys" +description = "Low-level rust bindings to libcuvs" +links = "cuvs" +version.workspace = true +edition.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] + +[build-dependencies] +cmake = ">=0.1" +bindgen = ">=0.69" diff --git a/rust/cuvs-sys/build.rs b/rust/cuvs-sys/build.rs new file mode 100644 index 0000000000..816a6f2592 --- /dev/null +++ b/rust/cuvs-sys/build.rs @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::env; +use std::io::BufRead; +use std::path::PathBuf; + +/* + TODO: + * would be nice to use already built versions of libcuvs_c / libcuvs + if they already existed, but this might not be possible here using cmake-rs + (https://github.com/rust-lang/cmake-rs/issues/111) + * figure out how this works with rust packaging: does the c++ code + need to be in a subdirectory? If so would a symlink work here + should we be using static linking ? +*/ +fn main() { + // build the cuvs c-api library with cmake, and link it into this crate + let cuvs_build = cmake::Config::new("../../cpp") + .configure_arg("-DBUILD_TESTS:BOOL=OFF") + .configure_arg("-DBUILD_C_LIBRARY:BOOL=ON") + .build(); + + println!( + "cargo:rustc-link-search=native={}/lib", + cuvs_build.display() + ); + println!("cargo:rustc-link-lib=dylib=cuvs_c"); + println!("cargo:rustc-link-lib=dylib=cudart"); + + // we need some extra flags both to link against cuvs, and also to run bindgen + // specifically we need to: + // * -I flags to set the include path to pick up cudaruntime.h during bindgen + // * -rpath-link settings to link to libraft/libcuvs.so etc during the link + // Rather than redefine the logic to set all these things, lets pick up the values from + // the cuvs cmake build in its CMakeCache.txt and set from there + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + + let cmake_cache: Vec = std::io::BufReader::new( + std::fs::File::open(format!("{}/build/CMakeCache.txt", out_path.display())) + .expect("Failed to open cuvs CMakeCache.txt"), + ) + .lines() + .map(|x| x.expect("Couldn't parse line from CMakeCache.txt")) + .collect(); + + let cmake_cxx_flags = cmake_cache + .iter() + .find(|x| x.starts_with("CMAKE_CXX_FLAGS:STRING=")) + .expect("failed to find CMAKE_CXX_FLAGS in CMakeCache.txt") + .strip_prefix("CMAKE_CXX_FLAGS:STRING=") + .unwrap(); + + let cmake_linker_flags = cmake_cache + .iter() + .find(|x| x.starts_with("CMAKE_EXE_LINKER_FLAGS:STRING=")) + .expect("failed to find CMAKE_EXE_LINKER_FLAGS in CMakeCache.txt") + .strip_prefix("CMAKE_EXE_LINKER_FLAGS:STRING=") + .unwrap(); + + // need to propagate the rpath-link settings to dependent crates =( + // (this will get added as DEP_CUVS_CMAKE_LINKER_ARGS in dependent crates) + println!("cargo:cmake_linker_flags={}", cmake_linker_flags); + + // add the required rpath-link flags to the cargo build + for flag in cmake_linker_flags.split(' ') { + if flag.starts_with("-Wl,-rpath-link") { + println!("cargo:rustc-link-arg={}", flag); + } + } + + // run bindgen to automatically create rust bindings for the cuvs c-api + bindgen::Builder::default() + .header("cuvs_c_wrapper.h") + .clang_arg("-I../../cpp/include") + // needed to find cudaruntime.h + .clang_args(cmake_cxx_flags.split(' ')) + // include dlpack from the cmake build dependencies + .clang_arg(format!( + "-I{}/build/_deps/dlpack-src/include/", + out_path.display() + )) + // add `must_use' declarations to functions returning cuvsError_t + // (so that if you don't check the error code a compile warning is + // generated) + .must_use_type("cuvsError_t") + // Only generate bindings for cuvs/cagra types and functions + .allowlist_type("(cuvs|cagra|DL).*") + .allowlist_function("(cuvs|cagra).*") + .rustified_enum("(cuvs|cagra|DL).*") + // also need some basic cuda mem functions + // (TODO: should we be adding in RMM support instead here?) + .allowlist_function("(cudaMalloc|cudaFree|cudaMemcpy)") + .rustified_enum("cudaError") + .generate() + .expect("Unable to generate cagra_c bindings") + .write_to_file(out_path.join("cuvs_bindings.rs")) + .expect("Failed to write generated rust bindings"); +} diff --git a/rust/cuvs-sys/cuvs_c_wrapper.h b/rust/cuvs-sys/cuvs_c_wrapper.h new file mode 100644 index 0000000000..ccca82632d --- /dev/null +++ b/rust/cuvs-sys/cuvs_c_wrapper.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// wrapper file containing all the C-API's we should automatically be creating rust +// bindings for +#include +#include diff --git a/rust/cuvs-sys/src/lib.rs b/rust/cuvs-sys/src/lib.rs new file mode 100644 index 0000000000..8a261a0524 --- /dev/null +++ b/rust/cuvs-sys/src/lib.rs @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ignore warnings from bindgen +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused_attributes)] + +// include the generated cuvs_bindings.rs file directly in here +// (this file is automatically generated by bindgen in build.rs) +include!(concat!(env!("OUT_DIR"), "/cuvs_bindings.rs")); + +#[cfg(test)] +mod tests { + use super::*; + // some super basic tests here to make sure we can call into the cuvs library + // the actual logic will be tested out through the higher level bindings + + #[test] + fn test_create_cagra_index() { + unsafe { + let mut index = core::mem::MaybeUninit::::uninit(); + assert_eq!( + cuvsCagraIndexCreate(index.as_mut_ptr()), + cuvsError_t::CUVS_SUCCESS + ); + let index = index.assume_init(); + assert_eq!(cuvsCagraIndexDestroy(index), cuvsError_t::CUVS_SUCCESS); + } + } + + #[test] + fn test_create_resources() { + unsafe { + let mut res: cuvsResources_t = 0; + assert_eq!(cuvsResourcesCreate(&mut res), cuvsError_t::CUVS_SUCCESS); + assert_eq!(cuvsResourcesDestroy(res), cuvsError_t::CUVS_SUCCESS); + } + } +} diff --git a/rust/cuvs/Cargo.toml b/rust/cuvs/Cargo.toml new file mode 100644 index 0000000000..cc52db026e --- /dev/null +++ b/rust/cuvs/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cuvs" +description = "RAPIDS vector search library" +version.workspace = true +edition.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +ffi = { package = "cuvs-sys", path = "../cuvs-sys" } +ndarray = "0.15" + +[dev-dependencies] +ndarray-rand = "*" diff --git a/rust/cuvs/build.rs b/rust/cuvs/build.rs new file mode 100644 index 0000000000..fde400dc40 --- /dev/null +++ b/rust/cuvs/build.rs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::env; + +fn main() { + // add the required rpath-link flags to the cargo build + // TODO: ... this isn't great, there must be a way to propagate this directly without hacks like + // this + let cmake_linker_flags = env::var("DEP_CUVS_CMAKE_LINKER_FLAGS").unwrap(); + for flag in cmake_linker_flags.split(' ') { + if flag.starts_with("-Wl,-rpath-link") { + println!("cargo:rustc-link-arg={}", flag); + } + } +} diff --git a/rust/cuvs/examples/cagra.rs b/rust/cuvs/examples/cagra.rs new file mode 100644 index 0000000000..ccc1466dd9 --- /dev/null +++ b/rust/cuvs/examples/cagra.rs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use cuvs::cagra::{Index, IndexParams, SearchParams}; +use cuvs::{ManagedTensor, Resources, Result}; + +use ndarray::s; +use ndarray_rand::rand_distr::Uniform; +use ndarray_rand::RandomExt; + +/// Example showing how to index and search data with CAGRA +fn cagra_example() -> Result<()> { + let res = Resources::new()?; + + // Create a new random dataset to index + let n_datapoints = 65536; + let n_features = 512; + let dataset = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + // build the cagra index + let build_params = IndexParams::new()?; + let index = Index::build(&res, &build_params, &dataset)?; + println!( + "Indexed {}x{} datapoints into cagra index", + n_datapoints, n_features + ); + + // use the first 4 points from the dataset as queries : will test that we get them back + // as their own nearest neighbor + let n_queries = 4; + let queries = dataset.slice(s![0..n_queries, ..]); + + let k = 10; + + // CAGRA search API requires queries and outputs to be on device memory + // copy query data over, and allocate new device memory for the distances/ neighbors + // outputs + let queries = ManagedTensor::from(&queries).to_device(&res)?; + let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); + let neighbors = ManagedTensor::from(&neighbors_host).to_device(&res)?; + + let mut distances_host = ndarray::Array::::zeros((n_queries, k)); + let distances = ManagedTensor::from(&distances_host).to_device(&res)?; + + let search_params = SearchParams::new()?; + + index.search(&res, &search_params, &queries, &neighbors, &distances)?; + + // Copy back to host memory + distances.to_host(&res, &mut distances_host)?; + neighbors.to_host(&res, &mut neighbors_host)?; + + // nearest neighbors should be themselves, since queries are from the + // dataset + println!("Neighbors {:?}", neighbors_host); + println!("Distances {:?}", distances_host); + Ok(()) +} + +fn main() { + if let Err(e) = cagra_example() { + println!("Failed to run CAGRA: {:?}", e); + } +} diff --git a/rust/cuvs/src/cagra/index.rs b/rust/cuvs/src/cagra/index.rs new file mode 100644 index 0000000000..43f0326766 --- /dev/null +++ b/rust/cuvs/src/cagra/index.rs @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::io::{stderr, Write}; + +use crate::cagra::{IndexParams, SearchParams}; +use crate::dlpack::ManagedTensor; +use crate::error::{check_cuvs, Result}; +use crate::resources::Resources; + +#[derive(Debug)] +pub struct Index(ffi::cuvsCagraIndex_t); + +impl Index { + /// Builds a new index + pub fn build>( + res: &Resources, + params: &IndexParams, + dataset: T, + ) -> Result { + let dataset: ManagedTensor = dataset.into(); + let index = Index::new()?; + unsafe { + check_cuvs(ffi::cuvsCagraBuild(res.0, params.0, dataset.as_ptr(), index.0))?; + } + Ok(index) + } + + /// Creates a new empty index + pub fn new() -> Result { + unsafe { + let mut index = core::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsCagraIndexCreate(index.as_mut_ptr()))?; + Ok(Index(index.assume_init())) + } + } + + pub fn search( + self, + res: &Resources, + params: &SearchParams, + queries: &ManagedTensor, + neighbors: &ManagedTensor, + distances: &ManagedTensor, + ) -> Result<()> { + unsafe { + check_cuvs(ffi::cuvsCagraSearch( + res.0, + params.0, + self.0, + queries.as_ptr(), + neighbors.as_ptr(), + distances.as_ptr(), + )) + } + } +} + +impl Drop for Index { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsCagraIndexDestroy(self.0) }) { + write!(stderr(), "failed to call cagraIndexDestroy {:?}", e) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ndarray::s; + use ndarray_rand::rand_distr::Uniform; + use ndarray_rand::RandomExt; + + #[test] + fn test_cagra_index() { + let res = Resources::new().unwrap(); + + // Create a new random dataset to index + let n_datapoints = 256; + let n_features = 16; + let dataset = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + // build the cagra index + let build_params = IndexParams::new().unwrap(); + let index = + Index::build(&res, &build_params, &dataset).expect("failed to create cagra index"); + + // use the first 4 points from the dataset as queries : will test that we get them back + // as their own nearest neighbor + let n_queries = 4; + let queries = dataset.slice(s![0..n_queries, ..]); + + let k = 10; + + // CAGRA search API requires queries and outputs to be on device memory + // copy query data over, and allocate new device memory for the distances/ neighbors + // outputs + let queries = ManagedTensor::from(&queries).to_device(&res).unwrap(); + let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); + let neighbors = ManagedTensor::from(&neighbors_host) + .to_device(&res) + .unwrap(); + + let mut distances_host = ndarray::Array::::zeros((n_queries, k)); + let distances = ManagedTensor::from(&distances_host) + .to_device(&res) + .unwrap(); + + let search_params = SearchParams::new().unwrap(); + + index + .search(&res, &search_params, &queries, &neighbors, &distances) + .unwrap(); + + // Copy back to host memory + distances.to_host(&res, &mut distances_host).unwrap(); + neighbors.to_host(&res, &mut neighbors_host).unwrap(); + + // nearest neighbors should be themselves, since queries are from the + // dataset + assert_eq!(neighbors_host[[0, 0]], 0); + assert_eq!(neighbors_host[[1, 0]], 1); + assert_eq!(neighbors_host[[2, 0]], 2); + assert_eq!(neighbors_host[[3, 0]], 3); + } +} diff --git a/rust/cuvs/src/cagra/index_params.rs b/rust/cuvs/src/cagra/index_params.rs new file mode 100644 index 0000000000..656ab4a9c1 --- /dev/null +++ b/rust/cuvs/src/cagra/index_params.rs @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{check_cuvs, Result}; +use std::fmt; +use std::io::{stderr, Write}; + +pub type BuildAlgo = ffi::cuvsCagraGraphBuildAlgo; + +/// Supplemental parameters to build CAGRA Index +pub struct IndexParams(pub ffi::cuvsCagraIndexParams_t); + +impl IndexParams { + pub fn new() -> Result { + unsafe { + let mut params = core::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsCagraIndexParamsCreate(params.as_mut_ptr()))?; + Ok(IndexParams(params.assume_init())) + } + } + + /// Degree of input graph for pruning + pub fn set_intermediate_graph_degree(self, intermediate_graph_degree: usize) -> IndexParams { + unsafe { + (*self.0).intermediate_graph_degree = intermediate_graph_degree; + } + self + } + + /// Degree of output graph + pub fn set_graph_degree(self, graph_degree: usize) -> IndexParams { + unsafe { + (*self.0).graph_degree = graph_degree; + } + self + } + + /// ANN algorithm to build knn graph + pub fn set_build_algo(self, build_algo: BuildAlgo) -> IndexParams { + unsafe { + (*self.0).build_algo = build_algo; + } + self + } + + /// Number of iterations to run if building with NN_DESCENT + pub fn set_nn_descent_niter(self, nn_descent_niter: usize) -> IndexParams { + unsafe { + (*self.0).nn_descent_niter = nn_descent_niter; + } + self + } +} + +impl fmt::Debug for IndexParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // custom debug trait here, default value will show the pointer address + // for the inner params object which isn't that useful. + write!(f, "IndexParams {{ params: {:?} }}", unsafe { *self.0 }) + } +} + +impl Drop for IndexParams { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsCagraIndexParamsDestroy(self.0) }) { + write!( + stderr(), + "failed to call cuvsCagraIndexParamsDestroy {:?}", + e + ) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_index_params() { + let params = IndexParams::new() + .unwrap() + .set_intermediate_graph_degree(128) + .set_graph_degree(16) + .set_build_algo(BuildAlgo::NN_DESCENT) + .set_nn_descent_niter(10); + + // make sure the setters actually updated internal representation on the c-struct + unsafe { + assert_eq!((*params.0).graph_degree, 16); + assert_eq!((*params.0).intermediate_graph_degree, 128); + assert_eq!((*params.0).build_algo, BuildAlgo::NN_DESCENT); + assert_eq!((*params.0).nn_descent_niter, 10); + } + } +} diff --git a/rust/cuvs/src/cagra/mod.rs b/rust/cuvs/src/cagra/mod.rs new file mode 100644 index 0000000000..55705c27a1 --- /dev/null +++ b/rust/cuvs/src/cagra/mod.rs @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod index; +mod index_params; +mod search_params; + +pub use index::Index; +pub use index_params::{BuildAlgo, IndexParams}; +pub use search_params::{HashMode, SearchAlgo, SearchParams}; diff --git a/rust/cuvs/src/cagra/search_params.rs b/rust/cuvs/src/cagra/search_params.rs new file mode 100644 index 0000000000..11ac92bdd2 --- /dev/null +++ b/rust/cuvs/src/cagra/search_params.rs @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{check_cuvs, Result}; +use std::fmt; +use std::io::{stderr, Write}; + +pub type SearchAlgo = ffi::cuvsCagraSearchAlgo; +pub type HashMode = ffi::cuvsCagraHashMode; + +/// Supplemental parameters to search CAGRA index +pub struct SearchParams(pub ffi::cuvsCagraSearchParams_t); + +impl SearchParams { + pub fn new() -> Result { + unsafe { + let mut params = core::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsCagraSearchParamsCreate(params.as_mut_ptr()))?; + Ok(SearchParams(params.assume_init())) + } + } + + /// Maximum number of queries to search at the same time (batch size). Auto select when 0 + pub fn set_max_queries(self, max_queries: usize) -> SearchParams { + unsafe { + (*self.0).max_queries = max_queries; + } + self + } + + /// Number of intermediate search results retained during the search. + /// This is the main knob to adjust trade off between accuracy and search speed. + /// Higher values improve the search accuracy + pub fn set_itopk_size(self, itopk_size: usize) -> SearchParams { + unsafe { + (*self.0).itopk_size = itopk_size; + } + self + } + + /// Upper limit of search iterations. Auto select when 0. + pub fn set_max_iterations(self, max_iterations: usize) -> SearchParams { + unsafe { + (*self.0).max_iterations = max_iterations; + } + self + } + + /// Which search implementation to use. + pub fn set_algo(self, algo: SearchAlgo) -> SearchParams { + unsafe { + (*self.0).algo = algo; + } + self + } + + /// Number of threads used to calculate a single distance. 4, 8, 16, or 32. + pub fn set_team_size(self, team_size: usize) -> SearchParams { + unsafe { + (*self.0).team_size = team_size; + } + self + } + + /// Lower limit of search iterations. + pub fn set_min_iterations(self, min_iterations: usize) -> SearchParams { + unsafe { + (*self.0).min_iterations = min_iterations; + } + self + } + + /// Thread block size. 0, 64, 128, 256, 512, 1024. Auto selection when 0. + pub fn set_thread_block_size(self, thread_block_size: usize) -> SearchParams { + unsafe { + (*self.0).thread_block_size = thread_block_size; + } + self + } + + /// Hashmap type. Auto selection when AUTO. + pub fn set_hashmap_mode(self, hashmap_mode: HashMode) -> SearchParams { + unsafe { + (*self.0).hashmap_mode = hashmap_mode; + } + self + } + + /// Lower limit of hashmap bit length. More than 8. + pub fn set_hashmap_min_bitlen(self, hashmap_min_bitlen: usize) -> SearchParams { + unsafe { + (*self.0).hashmap_min_bitlen = hashmap_min_bitlen; + } + self + } + + /// Upper limit of hashmap fill rate. More than 0.1, less than 0.9. + pub fn set_hashmap_max_fill_rate(self, hashmap_max_fill_rate: f32) -> SearchParams { + unsafe { + (*self.0).hashmap_max_fill_rate = hashmap_max_fill_rate; + } + self + } + + /// Number of iterations of initial random seed node selection. 1 or more. + pub fn set_num_random_samplings(self, num_random_samplings: u32) -> SearchParams { + unsafe { + (*self.0).num_random_samplings = num_random_samplings; + } + self + } + + /// Bit mask used for initial random seed node selection. + pub fn set_rand_xor_mask(self, rand_xor_mask: u64) -> SearchParams { + unsafe { + (*self.0).rand_xor_mask = rand_xor_mask; + } + self + } +} + +impl fmt::Debug for SearchParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // custom debug trait here, default value will show the pointer address + // for the inner params object which isn't that useful. + write!(f, "SearchParams {{ params: {:?} }}", unsafe { *self.0 }) + } +} + +impl Drop for SearchParams { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsCagraSearchParamsDestroy(self.0) }) { + write!( + stderr(), + "failed to call cuvsCagraSearchParamsDestroy {:?}", + e + ) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search_params() { + let params = SearchParams::new().unwrap().set_itopk_size(128); + + unsafe { + assert_eq!((*params.0).itopk_size, 128); + } + } +} diff --git a/rust/cuvs/src/dlpack.rs b/rust/cuvs/src/dlpack.rs new file mode 100644 index 0000000000..b86959db12 --- /dev/null +++ b/rust/cuvs/src/dlpack.rs @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::convert::From; + +use crate::error::{check_cuda, Result}; +use crate::resources::Resources; + +#[derive(Debug)] +pub struct ManagedTensor(ffi::DLManagedTensor); + +pub trait IntoDtype { + fn ffi_dtype() -> ffi::DLDataType; +} + +impl ManagedTensor { + pub fn as_ptr(&self) -> *mut ffi::DLManagedTensor { + &self.0 as *const _ as *mut _ + } + + fn bytes(&self) -> usize { + // figure out how many bytes to allocate + let mut bytes: usize = 1; + for x in 0..self.0.dl_tensor.ndim { + bytes *= unsafe { (*self.0.dl_tensor.shape.add(x as usize)) as usize }; + } + bytes *= (self.0.dl_tensor.dtype.bits / 8) as usize; + bytes + } + + pub fn to_device(&self, _res: &Resources) -> Result { + unsafe { + let bytes = self.bytes(); + let mut device_data: *mut std::ffi::c_void = std::ptr::null_mut(); + + // allocate storage, copy over + check_cuda(ffi::cudaMalloc(&mut device_data as *mut _, bytes))?; + check_cuda(ffi::cudaMemcpy( + device_data, + self.0.dl_tensor.data, + bytes, + ffi::cudaMemcpyKind_cudaMemcpyDefault, + ))?; + + let mut ret = self.0.clone(); + ret.dl_tensor.data = device_data; + // call cudaFree automatically to clean up data + ret.deleter = Some(cuda_free_tensor); + ret.dl_tensor.device.device_type = ffi::DLDeviceType::kDLCUDA; + + Ok(ManagedTensor(ret)) + } + } + pub fn to_host< + T: IntoDtype, + S: ndarray::RawData + ndarray::RawDataMut, + D: ndarray::Dimension, + >( + &self, + _res: &Resources, + arr: &mut ndarray::ArrayBase, + ) -> Result<()> { + unsafe { + let bytes = self.bytes(); + check_cuda(ffi::cudaMemcpy( + arr.as_mut_ptr() as *mut std::ffi::c_void, + self.0.dl_tensor.data, + bytes, + ffi::cudaMemcpyKind_cudaMemcpyDefault, + ))?; + + Ok(()) + } + } +} + +unsafe extern "C" fn cuda_free_tensor(self_: *mut ffi::DLManagedTensor) { + let _ = ffi::cudaFree((*self_).dl_tensor.data); +} + +/// Create a non-owning view of a Tensor from a ndarray +impl, D: ndarray::Dimension> + From<&ndarray::ArrayBase> for ManagedTensor +{ + fn from(arr: &ndarray::ArrayBase) -> Self { + // There is a draft PR out right now for creating dlpack directly from ndarray + // right now, but until its merged we have to implement ourselves + //https://github.com/rust-ndarray/ndarray/pull/1306/files + unsafe { + let mut ret = std::mem::MaybeUninit::::uninit(); + let tensor = ret.as_mut_ptr(); + (*tensor).data = arr.as_ptr() as *mut std::os::raw::c_void; + (*tensor).device = ffi::DLDevice { + device_type: ffi::DLDeviceType::kDLCPU, + device_id: 0, + }; + (*tensor).byte_offset = 0; + (*tensor).strides = std::ptr::null_mut(); // TODO: error if not rowmajor + (*tensor).ndim = arr.ndim() as i32; + (*tensor).shape = arr.shape().as_ptr() as *mut _; + (*tensor).dtype = T::ffi_dtype(); + ManagedTensor(ffi::DLManagedTensor { + dl_tensor: ret.assume_init(), + manager_ctx: std::ptr::null_mut(), + deleter: None, + }) + } + } +} + +impl Drop for ManagedTensor { + fn drop(&mut self) { + unsafe { + if let Some(deleter) = self.0.deleter { + deleter(&mut self.0 as *mut _); + } + } + } +} + +impl IntoDtype for f32 { + fn ffi_dtype() -> ffi::DLDataType { + ffi::DLDataType { + code: ffi::DLDataTypeCode::kDLFloat as _, + bits: 32, + lanes: 1, + } + } +} + +impl IntoDtype for f64 { + fn ffi_dtype() -> ffi::DLDataType { + ffi::DLDataType { + code: ffi::DLDataTypeCode::kDLFloat as _, + bits: 64, + lanes: 1, + } + } +} + +impl IntoDtype for i32 { + fn ffi_dtype() -> ffi::DLDataType { + ffi::DLDataType { + code: ffi::DLDataTypeCode::kDLInt as _, + bits: 32, + lanes: 1, + } + } +} + +impl IntoDtype for u32 { + fn ffi_dtype() -> ffi::DLDataType { + ffi::DLDataType { + code: ffi::DLDataTypeCode::kDLUInt as _, + bits: 32, + lanes: 1, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_ndarray() { + let arr = ndarray::Array::::zeros((8, 4)); + + let tensor = unsafe { (*(ManagedTensor::from(&arr).as_ptr())).dl_tensor }; + + assert_eq!(tensor.ndim, 2); + + // make sure we can get the shape ok + assert_eq!(unsafe { *tensor.shape }, 8); + assert_eq!(unsafe { *tensor.shape.add(1) }, 4); + } +} diff --git a/rust/cuvs/src/error.rs b/rust/cuvs/src/error.rs new file mode 100644 index 0000000000..618106aba7 --- /dev/null +++ b/rust/cuvs/src/error.rs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::fmt; + +#[derive(Debug, Clone)] +pub enum Error { + CudaError(ffi::cudaError_t), + CuvsError(ffi::cuvsError_t), +} + +impl std::error::Error for Error {} + +pub type Result = std::result::Result; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::CudaError(cuda_error) => write!(f, "cudaError={:?}", cuda_error), + Error::CuvsError(cuvs_error) => write!(f, "cuvsError={:?}", cuvs_error), + } + } +} + +/// Simple wrapper to convert a cuvsError_t into a Result +pub fn check_cuvs(err: ffi::cuvsError_t) -> Result<()> { + match err { + ffi::cuvsError_t::CUVS_SUCCESS => Ok(()), + _ => Err(Error::CuvsError(err)), + } +} + +pub fn check_cuda(err: ffi::cudaError_t) -> Result<()> { + match err { + ffi::cudaError::cudaSuccess => Ok(()), + _ => Err(Error::CudaError(err)), + } +} diff --git a/rust/cuvs/src/lib.rs b/rust/cuvs/src/lib.rs new file mode 100644 index 0000000000..7a6f847f5d --- /dev/null +++ b/rust/cuvs/src/lib.rs @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pub mod cagra; +mod dlpack; +mod error; +mod resources; + +pub use dlpack::ManagedTensor; +pub use error::{Error, Result}; +pub use resources::Resources; diff --git a/rust/cuvs/src/resources.rs b/rust/cuvs/src/resources.rs new file mode 100644 index 0000000000..ad7113e6be --- /dev/null +++ b/rust/cuvs/src/resources.rs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{check_cuvs, Result}; +use std::io::{stderr, Write}; + +#[derive(Debug)] +pub struct Resources(pub ffi::cuvsResources_t); + +impl Resources { + pub fn new() -> Result { + let mut res: ffi::cuvsResources_t = 0; + unsafe { + check_cuvs(ffi::cuvsResourcesCreate(&mut res))?; + } + Ok(Resources(res)) + } +} + +impl Drop for Resources { + fn drop(&mut self) { + unsafe { + if let Err(e) = check_cuvs(ffi::cuvsResourcesDestroy(self.0)) { + write!(stderr(), "failed to call cuvsResourcesDestroy {:?}", e) + .expect("failed to write to stderr"); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_resources_create() { + let _ = Resources::new(); + } +} From b94c1b531f9442ecfb50140c3311bf055a7d7aef Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 5 Mar 2024 11:20:32 -0800 Subject: [PATCH 005/623] Expose exception text to C, Python and Rust API's (#39) Previously when something went wrong in the Rust or C API - all the end user would see is a `CUVS_ERROR` return code with no extra indication of what went wrong. This change exposes the exception text to both the C and Rust api's, and provides a convenience method to automatically catch c++ exceptions, and convert the exception into an error code with the text set appropiately. Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Dante Gama Dessavre (https://github.com/dantegd) URL: https://github.com/rapidsai/cuvs/pull/39 --- cpp/include/cuvs/core/c_api.h | 10 ++++ cpp/include/cuvs/core/exceptions.hpp | 45 ++++++++++++++++ cpp/src/core/c_api.cpp | 38 ++++++------- cpp/src/neighbors/cagra_c.cpp | 62 +++++----------------- python/cuvs/cuvs/common/CMakeLists.txt | 2 +- python/cuvs/cuvs/common/c_api.pxd | 1 + python/cuvs/cuvs/common/exceptions.pyx | 37 +++++++++++++ python/cuvs/cuvs/neighbors/cagra/cagra.pyx | 37 ++++--------- rust/cuvs/src/cagra/index.rs | 7 ++- rust/cuvs/src/cagra/index_params.rs | 2 +- rust/cuvs/src/error.rs | 26 ++++++++- 11 files changed, 167 insertions(+), 100 deletions(-) create mode 100644 cpp/include/cuvs/core/exceptions.hpp create mode 100644 python/cuvs/cuvs/common/exceptions.pyx diff --git a/cpp/include/cuvs/core/c_api.h b/cpp/include/cuvs/core/c_api.h index b500329169..eceb917ff3 100644 --- a/cpp/include/cuvs/core/c_api.h +++ b/cpp/include/cuvs/core/c_api.h @@ -67,6 +67,16 @@ cuvsError_t cuvsResourcesDestroy(cuvsResources_t res); */ cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream); +/** @brief Returns a string describing the last seen error on this thread, or + * NULL if the last function succeeded. + */ +const char* cuvsGetLastErrorText(); + +/** + * @brief Sets a string describing an error seen on the thread. Passing NULL + * clears any previously seen error message. + */ +void cuvsSetLastErrorText(const char* error); #ifdef __cplusplus } #endif diff --git a/cpp/include/cuvs/core/exceptions.hpp b/cpp/include/cuvs/core/exceptions.hpp new file mode 100644 index 0000000000..01ee42151d --- /dev/null +++ b/cpp/include/cuvs/core/exceptions.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "c_api.h" + +#include + +namespace cuvs::core { + +/** + * @brief Translates C++ exceptions into cuvs C-API error codes + */ +template +cuvsError_t translate_exceptions(Fn func) +{ + cuvsError_t status; + try { + func(); + status = CUVS_SUCCESS; + cuvsSetLastErrorText(NULL); + } catch (const std::exception& e) { + cuvsSetLastErrorText(e.what()); + status = CUVS_ERROR; + } catch (...) { + cuvsSetLastErrorText("unknown exception"); + status = CUVS_ERROR; + } + return status; +} +} // namespace cuvs::core diff --git a/cpp/src/core/c_api.cpp b/cpp/src/core/c_api.cpp index 133021d77f..7ddb4f3e4c 100644 --- a/cpp/src/core/c_api.cpp +++ b/cpp/src/core/c_api.cpp @@ -16,46 +16,42 @@ #include #include +#include #include #include #include #include +#include extern "C" cuvsError_t cuvsResourcesCreate(cuvsResources_t* res) { - cuvsError_t status; - try { + return cuvs::core::translate_exceptions([=] { auto res_ptr = new raft::resources{}; *res = reinterpret_cast(res_ptr); - status = CUVS_SUCCESS; - } catch (...) { - status = CUVS_ERROR; - } - return status; + }); } extern "C" cuvsError_t cuvsResourcesDestroy(cuvsResources_t res) { - cuvsError_t status; - try { + return cuvs::core::translate_exceptions([=] { auto res_ptr = reinterpret_cast(res); delete res_ptr; - status = CUVS_SUCCESS; - } catch (...) { - status = CUVS_ERROR; - } - return status; + }); } extern "C" cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream) { - cuvsError_t status; - try { + return cuvs::core::translate_exceptions([=] { auto res_ptr = reinterpret_cast(res); raft::resource::set_cuda_stream(*res_ptr, static_cast(stream)); - status = CUVS_SUCCESS; - } catch (...) { - status = CUVS_ERROR; - } - return status; + }); } + +thread_local std::string last_error_text = ""; + +extern "C" const char* cuvsGetLastErrorText() +{ + return last_error_text.empty() ? NULL : last_error_text.c_str(); +} + +extern "C" void cuvsSetLastErrorText(const char* error) { last_error_text = error ? error : ""; } diff --git a/cpp/src/neighbors/cagra_c.cpp b/cpp/src/neighbors/cagra_c.cpp index 70e268fb2f..9fdfe2c1e7 100644 --- a/cpp/src/neighbors/cagra_c.cpp +++ b/cpp/src/neighbors/cagra_c.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -96,17 +97,12 @@ void _search(cuvsResources_t res, extern "C" cuvsError_t cuvsCagraIndexCreate(cuvsCagraIndex_t* index) { - try { - *index = new cuvsCagraIndex{}; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions([=] { *index = new cuvsCagraIndex{}; }); } extern "C" cuvsError_t cuvsCagraIndexDestroy(cuvsCagraIndex_t index_c_ptr) { - try { + return cuvs::core::translate_exceptions([=] { auto index = *index_c_ptr; if (index.dtype.code == kDLFloat) { @@ -123,10 +119,7 @@ extern "C" cuvsError_t cuvsCagraIndexDestroy(cuvsCagraIndex_t index_c_ptr) delete index_ptr; } delete index_c_ptr; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t cuvsCagraBuild(cuvsResources_t res, @@ -134,7 +127,7 @@ extern "C" cuvsError_t cuvsCagraBuild(cuvsResources_t res, DLManagedTensor* dataset_tensor, cuvsCagraIndex_t index) { - try { + return cuvs::core::translate_exceptions([=] { auto dataset = dataset_tensor->dl_tensor; if (dataset.dtype.code == kDLFloat && dataset.dtype.bits == 32) { @@ -151,13 +144,7 @@ extern "C" cuvsError_t cuvsCagraBuild(cuvsResources_t res, dataset.dtype.code, dataset.dtype.bits); } - return CUVS_SUCCESS; - } catch (const std::exception& ex) { - std::cerr << "Error occurred: " << ex.what() << std::endl; - return CUVS_ERROR; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t cuvsCagraSearch(cuvsResources_t res, @@ -167,7 +154,7 @@ extern "C" cuvsError_t cuvsCagraSearch(cuvsResources_t res, DLManagedTensor* neighbors_tensor, DLManagedTensor* distances_tensor) { - try { + return cuvs::core::translate_exceptions([=] { auto queries = queries_tensor->dl_tensor; auto neighbors = neighbors_tensor->dl_tensor; auto distances = distances_tensor->dl_tensor; @@ -198,57 +185,36 @@ extern "C" cuvsError_t cuvsCagraSearch(cuvsResources_t res, queries.dtype.code, queries.dtype.bits); } - return CUVS_SUCCESS; - } catch (const std::exception& ex) { - std::cerr << "Error occurred: " << ex.what() << std::endl; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t cuvsCagraIndexParamsCreate(cuvsCagraIndexParams_t* params) { - try { + return cuvs::core::translate_exceptions([=] { *params = new cuvsCagraIndexParams{.intermediate_graph_degree = 128, .graph_degree = 64, .build_algo = IVF_PQ, .nn_descent_niter = 20}; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t params) { - try { - delete params; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions([=] { delete params; }); } extern "C" cuvsError_t cuvsCagraSearchParamsCreate(cuvsCagraSearchParams_t* params) { - try { + return cuvs::core::translate_exceptions([=] { *params = new cuvsCagraSearchParams{.itopk_size = 64, .search_width = 1, .hashmap_max_fill_rate = 0.5, .num_random_samplings = 1, .rand_xor_mask = 0x128394}; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t cuvsCagraSearchParamsDestroy(cuvsCagraSearchParams_t params) { - try { - delete params; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions([=] { delete params; }); } diff --git a/python/cuvs/cuvs/common/CMakeLists.txt b/python/cuvs/cuvs/common/CMakeLists.txt index 2cf1ff19c1..b477fdb322 100644 --- a/python/cuvs/cuvs/common/CMakeLists.txt +++ b/python/cuvs/cuvs/common/CMakeLists.txt @@ -13,7 +13,7 @@ # ============================================================================= # Set the list of Cython files to build -set(cython_sources cydlpack.pyx) +set(cython_sources cydlpack.pyx exceptions.pyx) set(linked_libraries cuvs::cuvs cuvs_c) # Build all of the Cython targets diff --git a/python/cuvs/cuvs/common/c_api.pxd b/python/cuvs/cuvs/common/c_api.pxd index 6addbf16e2..e4e62bf944 100644 --- a/python/cuvs/cuvs/common/c_api.pxd +++ b/python/cuvs/cuvs/common/c_api.pxd @@ -30,3 +30,4 @@ cdef extern from "cuvs/core/c_api.h": cuvsError_t cuvsResourcesCreate(cuvsResources_t* res) cuvsError_t cuvsResourcesDestroy(cuvsResources_t res) cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream) + const char * cuvsGetLastErrorText() diff --git a/python/cuvs/cuvs/common/exceptions.pyx b/python/cuvs/cuvs/common/exceptions.pyx new file mode 100644 index 0000000000..603e0711bc --- /dev/null +++ b/python/cuvs/cuvs/common/exceptions.pyx @@ -0,0 +1,37 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +from cuvs.common.c_api cimport cuvsError_t, cuvsGetLastErrorText + + +class CuvsException(Exception): + pass + + +def get_last_error_text(): + """ returns the last error description from the cuvs c-api """ + cdef const char* c_err = cuvsGetLastErrorText() + if c_err is NULL: + return + cdef bytes err = c_err + return err.decode("utf8") + + +def check_cuvs(status: cuvsError_t): + """ Converts a status code into an exception """ + if status == cuvsError_t.CUVS_ERROR: + raise CuvsException(get_last_error_text()) diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx index bf17fe6a55..04031414e2 100644 --- a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx @@ -52,6 +52,8 @@ from cuvs.common.c_api cimport ( cuvsResourcesCreate, ) +from cuvs.common.exceptions import check_cuvs + cdef class IndexParams: """ @@ -124,19 +126,13 @@ cdef class Index: cdef bool trained def __cinit__(self): - cdef cuvsError_t index_create_status - index_create_status = cuvsCagraIndexCreate(&self.index) self.trained = False - - if index_create_status == cuvsError_t.CUVS_ERROR: - raise RuntimeError("Failed to create index.") + check_cuvs(cuvsCagraIndexCreate(&self.index)) def __dealloc__(self): cdef cuvsError_t index_destroy_status if self.index is not NULL: - index_destroy_status = cuvsCagraIndexDestroy(self.index) - if index_destroy_status == cuvsError_t.CUVS_ERROR: - raise Exception("Failed to deallocate index.") + check_cuvs(cuvsCagraIndexDestroy(self.index)) @property def trained(self): @@ -203,9 +199,7 @@ def build_index(IndexParams index_params, dataset, resources=None): cdef cuvsResources_t res_ cdef cuvsError_t cstat - cstat = cuvsResourcesCreate(&res_) - if cstat == cuvsError_t.CUVS_ERROR: - raise RuntimeError("Error creating Device Reources.") + check_cuvs(cuvsResourcesCreate(&res_)) cdef Index idx = Index() cdef cuvsError_t build_status @@ -214,17 +208,13 @@ def build_index(IndexParams index_params, dataset, resources=None): cdef cuvsCagraIndexParams* params = index_params.params with cuda_interruptible(): - build_status = cuvsCagraBuild( + check_cuvs(cuvsCagraBuild( res_, params, dataset_dlpack, idx.index - ) - - if build_status == cuvsError_t.CUVS_ERROR: - raise RuntimeError("Index failed to build.") - else: - idx.trained = True + )) + idx.trained = True return idx @@ -451,9 +441,7 @@ def search(SearchParams search_params, cdef cuvsResources_t res_ cdef cuvsError_t cstat - cstat = cuvsResourcesCreate(&res_) - if cstat == cuvsError_t.CUVS_ERROR: - raise RuntimeError("Error creating Device Reources.") + check_cuvs(cuvsResourcesCreate(&res_)) # todo(dgd): we can make the check of dtype a parameter of wrap_array # in RAFT to make this a single call @@ -487,16 +475,13 @@ def search(SearchParams search_params, cydlpack.dlpack_c(distances_cai) with cuda_interruptible(): - search_status = cuvsCagraSearch( + check_cuvs(cuvsCagraSearch( res_, params, index.index, queries_dlpack, neighbors_dlpack, distances_dlpack - ) - - if search_status == cuvsError_t.CUVS_ERROR: - raise RuntimeError("Search failed.") + )) return (distances, neighbors) diff --git a/rust/cuvs/src/cagra/index.rs b/rust/cuvs/src/cagra/index.rs index 43f0326766..3c45efafd2 100644 --- a/rust/cuvs/src/cagra/index.rs +++ b/rust/cuvs/src/cagra/index.rs @@ -34,7 +34,12 @@ impl Index { let dataset: ManagedTensor = dataset.into(); let index = Index::new()?; unsafe { - check_cuvs(ffi::cuvsCagraBuild(res.0, params.0, dataset.as_ptr(), index.0))?; + check_cuvs(ffi::cuvsCagraBuild( + res.0, + params.0, + dataset.as_ptr(), + index.0, + ))?; } Ok(index) } diff --git a/rust/cuvs/src/cagra/index_params.rs b/rust/cuvs/src/cagra/index_params.rs index 656ab4a9c1..7ff68d3f3f 100644 --- a/rust/cuvs/src/cagra/index_params.rs +++ b/rust/cuvs/src/cagra/index_params.rs @@ -69,7 +69,7 @@ impl fmt::Debug for IndexParams { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // custom debug trait here, default value will show the pointer address // for the inner params object which isn't that useful. - write!(f, "IndexParams {{ params: {:?} }}", unsafe { *self.0 }) + write!(f, "IndexParams({:?})", unsafe { *self.0 }) } } diff --git a/rust/cuvs/src/error.rs b/rust/cuvs/src/error.rs index 618106aba7..53c68a9767 100644 --- a/rust/cuvs/src/error.rs +++ b/rust/cuvs/src/error.rs @@ -16,13 +16,20 @@ use std::fmt; +#[derive(Debug, Clone)] +pub struct CuvsError { + code: ffi::cuvsError_t, + text: String, +} + #[derive(Debug, Clone)] pub enum Error { CudaError(ffi::cudaError_t), - CuvsError(ffi::cuvsError_t), + CuvsError(CuvsError), } impl std::error::Error for Error {} +impl std::error::Error for CuvsError {} pub type Result = std::result::Result; @@ -35,11 +42,26 @@ impl fmt::Display for Error { } } +impl fmt::Display for CuvsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}:{}", self.code, self.text) + } +} + /// Simple wrapper to convert a cuvsError_t into a Result pub fn check_cuvs(err: ffi::cuvsError_t) -> Result<()> { match err { ffi::cuvsError_t::CUVS_SUCCESS => Ok(()), - _ => Err(Error::CuvsError(err)), + _ => { + // get a description of the error from cuvs + let cstr = unsafe { + let text_ptr = ffi::cuvsGetLastErrorText(); + std::ffi::CStr::from_ptr(text_ptr) + }; + let text = std::string::String::from_utf8_lossy(cstr.to_bytes()).to_string(); + + Err(Error::CuvsError(CuvsError { code: err, text })) + } } } From d3478f729ab8479a14c4e4197faae87d55bb01bd Mon Sep 17 00:00:00 2001 From: Dante Gama Dessavre Date: Fri, 8 Mar 2024 11:15:34 -0600 Subject: [PATCH 006/623] Fix 'cuvs' conda package to only include Python artifacts and clean build.sh (#40) PR does the following: - [x] Stops the inclusion of C/C++ artifacts in the `cuvs` conda package - [x] Cleans `build.sh` from code that was copied from RAFT - [x] Adds `dlpack` to development environments - [x] Other small fixes and cleanups Authors: - Dante Gama Dessavre (https://github.com/dantegd) - Bradley Dice (https://github.com/bdice) - Ben Frederickson (https://github.com/benfred) Approvers: - Ben Frederickson (https://github.com/benfred) - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/40 --- build.sh | 52 +++----------- ci/build_wheel_cuvs.sh | 2 +- .../all_cuda-118_arch-aarch64.yaml | 1 + .../all_cuda-118_arch-x86_64.yaml | 1 + .../all_cuda-122_arch-aarch64.yaml | 1 + .../all_cuda-122_arch-x86_64.yaml | 1 + conda/recipes/cuvs/meta.yaml | 6 ++ conda/recipes/libcuvs/meta.yaml | 72 ++++++++++++++++--- dependencies.yaml | 7 +- python/cuvs/CMakeLists.txt | 13 +++- python/cuvs/pyproject.toml | 3 +- 11 files changed, 96 insertions(+), 63 deletions(-) diff --git a/build.sh b/build.sh index c6e09c5bf9..d53f7c3545 100755 --- a/build.sh +++ b/build.sh @@ -48,13 +48,13 @@ HELP="$0 [ ...] [ ...] [--cmake-args=\"\"] [--cache-tool= /dev/null 2>&1 - fi - - # Try to uninstall via pip if it is installed - if [ -x "$(command -v pip)" ]; then - echo "Using pip to uninstall cuvs-dask" - pip uninstall -y cuvs-dask - - # Otherwise, try to uninstall through conda if that's where things are installed - elif [ -x "$(command -v conda)" ] && [ "$INSTALL_PREFIX" == "$CONDA_PREFIX" ]; then - echo "Using conda to uninstall cuvs-dask" - conda uninstall -y cuvs-dask - - # Otherwise, fail - else - echo "Could not uninstall cuvs-dask from pip or conda. cuvs-dask package will need to be manually uninstalled." - fi - fi exit 0 fi @@ -261,24 +239,14 @@ if hasArg --allgpuarch; then BUILD_ALL_GPU_ARCH=1 fi - -# Append `-DFIND_CUVS_CPP=ON` to EXTRA_CMAKE_ARGS unless a user specified the option. -if [[ "${EXTRA_CMAKE_ARGS}" != *"DFIND_CUVS_CPP"* ]]; then - EXTRA_CMAKE_ARGS="${EXTRA_CMAKE_ARGS} -DFIND_CUVS_CPP=ON" -fi - if hasArg tests || (( ${NUMARGS} == 0 )); then BUILD_TESTS=ON CMAKE_TARGET="${CMAKE_TARGET};${TEST_TARGETS}" # Force compile library when needed test targets are specified - if [[ $CMAKE_TARGET == *"CLUSTER_TEST"* || \ - $CMAKE_TARGET == *"DISTANCE_TEST"* || \ - $CMAKE_TARGET == *"NEIGHBORS_ANN_CAGRA_TEST"* || \ - $CMAKE_TARGET == *"NEIGHBORS_ANN_IVF_TEST"* || \ - $CMAKE_TARGET == *"NEIGHBORS_ANN_NN_DESCENT_TEST"* || \ - $CMAKE_TARGET == *"NEIGHBORS_TEST"* || \ - $CMAKE_TARGET == *"STATS_TEST"* ]]; then + if [[ $CMAKE_TARGET == *"CAGRA_C_TEST"* || \ + $CMAKE_TARGET == *"INTEROP_TEST"* || \ + $CMAKE_TARGET == *"NEIGHBORS_ANN_CAGRA_TEST"* ]]; then echo "-- Enabling compiled lib for gtests" COMPILE_LIBRARY=ON fi @@ -304,13 +272,10 @@ if [[ ${CMAKE_TARGET} == "" ]]; then CMAKE_TARGET="all" fi -# Append `-DFIND_CUVS_CPP=ON` to EXTRA_CMAKE_ARGS unless a user specified the option. - - SKBUILD_EXTRA_CMAKE_ARGS="${EXTRA_CMAKE_ARGS}" if [[ "${EXTRA_CMAKE_ARGS}" != *"DFIND_CUVS_CPP"* ]]; then - SKBUILD_EXTRA_CMAKE_ARGS="${SKBUILD_EXTRA_CMAKE_ARGS} -DFIND_CUVS_CPP=ON" + SKBUILD_EXTRA_CMAKE_ARGS="${SKBUILD_EXTRA_CMAKE_ARGS};-DFIND_CUVS_CPP=ON" fi # If clean given, run it prior to any other steps @@ -353,7 +318,6 @@ if (( ${NUMARGS} == 0 )) || hasArg libcuvs || hasArg docs || hasArg tests || has -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ -DCMAKE_CUDA_ARCHITECTURES=${CUVS_CMAKE_CUDA_ARCHITECTURES} \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ - -DCUVS_COMPILE_LIBRARY=${COMPILE_LIBRARY} \ -DBUILD_C_LIBRARY=${COMPILE_LIBRARY} \ -DCUVS_NVTX=${NVTX} \ -DCUDA_LOG_COMPILE_TIME=${LOG_COMPILE_TIME} \ @@ -422,9 +386,9 @@ fi # Build and (optionally) install the cuvs Python package if (( ${NUMARGS} == 0 )) || hasArg python; then - SKBUILD_CONFIGURE_OPTIONS="${SKBUILD_EXTRA_CMAKE_ARGS}" \ + SKBUILD_CMAKE_ARGS="${SKBUILD_EXTRA_CMAKE_ARGS}" \ SKBUILD_BUILD_OPTIONS="-j${PARALLEL_LEVEL}" \ - python -m pip install --no-build-isolation --no-deps ${REPODIR}/python/cuvs + python -m pip install --no-build-isolation --no-deps -vvv ${REPODIR}/python/cuvs fi if hasArg docs; then diff --git a/ci/build_wheel_cuvs.sh b/ci/build_wheel_cuvs.sh index 238483b6e5..0fe28c2f7f 100755 --- a/ci/build_wheel_cuvs.sh +++ b/ci/build_wheel_cuvs.sh @@ -4,6 +4,6 @@ set -euo pipefail # Set up skbuild options. Enable sccache in skbuild config options -export SKBUILD_CONFIGURE_OPTIONS="-DDETECT_CONDA_ENV=OFF -DFIND_CUVS_CPP=OFF" +export SKBUILD_CMAKE_ARGS="-DDETECT_CONDA_ENV=OFF -DFIND_CUVS_CPP=OFF" ci/build_wheel.sh cuvs python/cuvs diff --git a/conda/environments/all_cuda-118_arch-aarch64.yaml b/conda/environments/all_cuda-118_arch-aarch64.yaml index e4f9223784..d711fc5554 100644 --- a/conda/environments/all_cuda-118_arch-aarch64.yaml +++ b/conda/environments/all_cuda-118_arch-aarch64.yaml @@ -20,6 +20,7 @@ dependencies: - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 +- dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-aarch64=11.* - gmock>=1.13.0 diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index a26314b229..b4c2c6857b 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -20,6 +20,7 @@ dependencies: - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 +- dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-64=11.* - gmock>=1.13.0 diff --git a/conda/environments/all_cuda-122_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml index 91d55917ad..85d96e5c94 100644 --- a/conda/environments/all_cuda-122_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -21,6 +21,7 @@ dependencies: - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 +- dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-aarch64=11.* - gmock>=1.13.0 diff --git a/conda/environments/all_cuda-122_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml index f27d131ffa..16cc655c63 100644 --- a/conda/environments/all_cuda-122_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -21,6 +21,7 @@ dependencies: - cupy>=12.0.0 - cxx-compiler - cython>=3.0.0 +- dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-64=11.* - gmock>=1.13.0 diff --git a/conda/recipes/cuvs/meta.yaml b/conda/recipes/cuvs/meta.yaml index 0902dd15a5..b2a8074007 100644 --- a/conda/recipes/cuvs/meta.yaml +++ b/conda/recipes/cuvs/meta.yaml @@ -22,6 +22,9 @@ build: ignore_run_exports_from: {% if cuda_major == "11" %} - {{ compiler('cuda11') }} + {% else %} + - {{ compiler('cuda') }} + - cuda-cudart-dev {% endif %} requirements: @@ -43,6 +46,7 @@ requirements: - cudatoolkit {% else %} - cuda-python >=12.0,<13.0a0 + - cuda-cudart-dev {% endif %} - cuda-version ={{ cuda_version }} - cython >=3.0.0 @@ -57,6 +61,8 @@ requirements: - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} {% if cuda_major == "11" %} - cudatoolkit + {% else %} + - cuda-cudart {% endif %} - pylibraft {{ minor_version }} - libcuvs {{ version }} diff --git a/conda/recipes/libcuvs/meta.yaml b/conda/recipes/libcuvs/meta.yaml index 4b6ff87e97..4ad3618c38 100644 --- a/conda/recipes/libcuvs/meta.yaml +++ b/conda/recipes/libcuvs/meta.yaml @@ -1,13 +1,12 @@ # Copyright (c) 2022-2024, NVIDIA CORPORATION. # Usage: - # conda build . -c conda-forge -c nvidia -c rapidsai - {% set version = environ['RAPIDS_PACKAGE_VERSION'].lstrip('v') + environ.get('VERSION_SUFFIX', '') %} - {% set minor_version = version.split('.')[0] + '.' + version.split('.')[1] %} - {% set cuda_version = '.'.join(environ['RAPIDS_CUDA_VERSION'].split('.')[:2]) %} - {% set cuda_major = cuda_version.split('.')[0] %} - {% set cuda_spec = ">=" + cuda_major ~ ",<" + (cuda_major | int + 1) ~ ".0a0" %} # i.e. >=11,<12.0a0 - {% set date_string = environ['RAPIDS_DATE_STRING'] %} +# conda build . -c conda-forge -c nvidia -c rapidsai +{% set version = environ['RAPIDS_PACKAGE_VERSION'].lstrip('v') + environ.get('VERSION_SUFFIX', '') %} +{% set minor_version = version.split('.')[0] + '.' + version.split('.')[1] %} +{% set cuda_version = '.'.join(environ['RAPIDS_CUDA_VERSION'].split('.')[:2]) %} +{% set cuda_major = cuda_version.split('.')[0] %} +{% set date_string = environ['RAPIDS_DATE_STRING'] %} package: name: libcuvs-split @@ -42,6 +41,13 @@ outputs: ignore_run_exports_from: {% if cuda_major == "11" %} - {{ compiler('cuda11') }} + {% else %} + - {{ compiler('cuda') }} + - cuda-cudart-dev + - libcublas-dev + - libcurand-dev + - libcusolver-dev + - libcusparse-dev {% endif %} requirements: build: @@ -70,6 +76,7 @@ outputs: - libcusparse {{ cuda11_libcusparse_host_version }} - libcusparse-dev {{ cuda11_libcusparse_host_version }} {% else %} + - cuda-cudart-dev - cuda-profiler-api - libcublas-dev - libcurand-dev @@ -79,6 +86,13 @@ outputs: run: - libraft ={{ minor_version }} - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} + {% if cuda_major != "11" %} + - cuda-cudart + - libcublas + - libcurand + - libcusolver + - libcusparse + {% endif %} about: home: https://rapids.ai/ license: Apache-2.0 @@ -93,6 +107,13 @@ outputs: ignore_run_exports_from: {% if cuda_major == "11" %} - {{ compiler('cuda11') }} + {% else %} + - {{ compiler('cuda') }} + - cuda-cudart-dev + - libcublas-dev + - libcurand-dev + - libcusolver-dev + - libcusparse-dev {% endif %} requirements: build: @@ -121,6 +142,7 @@ outputs: - libcusparse {{ cuda11_libcusparse_host_version }} - libcusparse-dev {{ cuda11_libcusparse_host_version }} {% else %} + - cuda-cudart-dev - cuda-profiler-api - libcublas-dev - libcurand-dev @@ -130,6 +152,13 @@ outputs: run: - libraft ={{ minor_version }} - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} + {% if cuda_major != "11" %} + - cuda-cudart + - libcublas + - libcurand + - libcusolver + - libcusparse + {% endif %} about: home: https://rapids.ai/ license: Apache-2.0 @@ -144,6 +173,13 @@ outputs: ignore_run_exports_from: {% if cuda_major == "11" %} - {{ compiler('cuda11') }} + {% else %} + - {{ compiler('cuda') }} + - cuda-cudart-dev + - libcublas-dev + - libcurand-dev + - libcusolver-dev + - libcusparse-dev {% endif %} requirements: build: @@ -160,7 +196,7 @@ outputs: - sysroot_{{ target_platform }} {{ sysroot_version }} host: - libraft ={{ minor_version }} - - libcuvs ={{ minor_version }} + - {{ pin_subpackage('libcuvs', exact=True) }} - cuda-version ={{ cuda_version }} {% if cuda_major == "11" %} - cuda-profiler-api {{ cuda11_cuda_profiler_api_run_version }} @@ -186,9 +222,15 @@ outputs: - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} {% if cuda_major == "11" %} - cudatoolkit + {% else %} + - cuda-cudart + - libcublas + - libcurand + - libcusolver + - libcusparse {% endif %} - libraft ={{ minor_version }} - - libcuvs ={{ minor_version }} + - {{ pin_subpackage('libcuvs', exact=True) }} - gmock {{ gtest_version }} - gtest {{ gtest_version }} about: @@ -205,6 +247,10 @@ outputs: ignore_run_exports_from: {% if cuda_major == "11" %} - {{ compiler('cuda11') }} + {% else %} + - {{ compiler('cuda') }} + - cuda-cudart-dev + - libcublas-dev {% endif %} requirements: build: @@ -221,13 +267,14 @@ outputs: - sysroot_{{ target_platform }} {{ sysroot_version }} host: - libraft ={{ minor_version }} - - libcuvs ={{ minor_version }} + - {{ pin_subpackage('libcuvs', exact=True) }} - cuda-version ={{ cuda_version }} {% if cuda_major == "11" %} - cuda-profiler-api {{ cuda11_cuda_profiler_api_run_version }} - libcublas {{ cuda11_libcublas_host_version }} - libcublas-dev {{ cuda11_libcublas_host_version }} {% else %} + - cuda-cudart-dev - cuda-profiler-api - libcublas-dev {% endif %} @@ -235,9 +282,12 @@ outputs: - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} {% if cuda_major == "11" %} - cudatoolkit + {% else %} + - cuda-cudart + - libcublas {% endif %} - libraft ={{ minor_version }} - - libcuvs ={{ minor_version }} + - {{ pin_subpackage('libcuvs', exact=True) }} about: home: https://rapids.ai/ license: Apache-2.0 diff --git a/dependencies.yaml b/dependencies.yaml index d7562ce573..7fff7811a8 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -47,6 +47,7 @@ files: rust: output: none includes: + - cuda_version - build - cuda - rust @@ -138,6 +139,7 @@ dependencies: packages: - &rmm_conda rmm==24.4.* - &pylibraft_conda pylibraft==24.4.* + - dlpack>=0.8,<1.0 - output_types: requirements packages: # pip recognizes the index as a global option for the requirements.txt file @@ -320,7 +322,7 @@ dependencies: - make - rust # clang/liblclang only needed for bindgen support - - clang + - clang - libclang build_wheels: common: @@ -380,7 +382,7 @@ dependencies: - matrix: {cuda: "11.*"} packages: - *pylibraft_cu11 - - {matrix: null, packages: [*rmm_conda]} + - {matrix: null, packages: [*pylibraft_conda]} test_python_common: common: - output_types: [conda, requirements, pyproject] @@ -391,5 +393,4 @@ dependencies: common: - output_types: [conda, requirements, pyproject] packages: - - *pylibraft_conda - scikit-learn diff --git a/python/cuvs/CMakeLists.txt b/python/cuvs/CMakeLists.txt index 5a5efe8f9c..33a9420e7f 100644 --- a/python/cuvs/CMakeLists.txt +++ b/python/cuvs/CMakeLists.txt @@ -33,11 +33,17 @@ project( C CXX CUDA ) +################################################################################ +# - User Options -------------------------------------------------------------- + option(FIND_CUVS_CPP "Search for existing CUVS C++ installations before defaulting to local files" OFF ) -message("- FIND_CUVS_CPP: ${FIND_CUVS_CPP}") +message("CUVS_PY: Searching for existing cuVS C/C++ installations before defaulting to local files: ${FIND_CUVS_CPP}") + +################################################################################ +# - Process User Options ------------------------------------------------------ include(../../fetch_rapids.cmake) include(rapids-cmake) @@ -50,7 +56,7 @@ rapids_cpm_init() # If the user requested it we attempt to find CUVS. if(FIND_CUVS_CPP) - find_package(cuvs ${cuvs_version}) + find_package(cuvs ${cuvs_version} REQUIRED) include(../../cpp/cmake/thirdparty/get_dlpack.cmake) else() set(cuvs_FOUND OFF) @@ -70,6 +76,9 @@ if(NOT cuvs_FOUND) install(TARGETS cuvs cuvs_c DESTINATION ${cython_lib_dir}) endif() +################################################################################ +# - Build Cython artifacts ----------------------------------------------------- + rapids_cython_init() add_subdirectory(cuvs/common) diff --git a/python/cuvs/pyproject.toml b/python/cuvs/pyproject.toml index 664cf29692..07578abf3a 100644 --- a/python/cuvs/pyproject.toml +++ b/python/cuvs/pyproject.toml @@ -38,7 +38,7 @@ requires-python = ">=3.9" dependencies = [ "cuda-python>=11.7.1,<12.0a0", "numpy>=1.23", - "rmm==24.4.*", + "pylibraft==24.4.*", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ "Intended Audience :: Developers", @@ -50,7 +50,6 @@ classifiers = [ [project.optional-dependencies] test = [ "cupy-cuda11x>=12.0.0", - "pylibraft==24.4.*", "pytest-cov", "pytest==7.*", "scikit-learn", From 506c14387878c6eb2eef8e998ca52b47f9ad3a4d Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Fri, 8 Mar 2024 23:32:40 +0100 Subject: [PATCH 007/623] API Documentation (#44) Building out initial API documentation for all the supported languages and algorithms. Authors: - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Ben Frederickson (https://github.com/benfred) URL: https://github.com/rapidsai/cuvs/pull/44 --- build.sh | 2 +- cpp/doxygen/Doxyfile | 1 + cpp/include/cuvs/core/c_api.h | 48 +-- cpp/include/cuvs/neighbors/cagra.h | 55 ++- cpp/include/cuvs/neighbors/cagra.hpp | 241 ++++++++++---- docs/source/api_docs.rst | 11 + docs/source/build.md | 312 +++++------------- docs/source/c_api.rst | 8 +- docs/source/c_api/core_c_api.rst | 20 +- docs/source/c_api/neighbors.rst | 14 + docs/source/c_api/neighbors_cagra_c.rst | 48 ++- docs/source/conf.py | 8 +- docs/source/cpp_api.rst | 6 +- docs/source/cpp_api/core_interop.rst | 1 - docs/source/cpp_api/neighbors.rst | 6 +- docs/source/cpp_api/neighbors_cagra.rst | 40 ++- docs/source/index.rst | 3 +- docs/source/python_api.rst | 8 +- docs/source/python_api/neighbors.rst | 12 + docs/source/python_api/neighbors_cagra.rst | 36 ++ docs/source/rust_api.rst | 8 + docs/source/sphinxext/github_link.py | 2 +- {cpp/examples => examples/cpp}/CMakeLists.txt | 0 {cpp/examples => examples/cpp}/README.md | 5 +- {cpp/examples => examples/cpp}/build.sh | 0 .../cpp}/cmake/thirdparty/fetch_rapids.cmake | 0 .../cpp}/cmake/thirdparty/get_cuvs.cmake | 0 .../cpp}/src/cagra_example.cu | 0 {cpp/examples => examples/cpp}/src/common.cuh | 0 python/cuvs/cuvs/neighbors/cagra/cagra.pyx | 7 + 30 files changed, 537 insertions(+), 365 deletions(-) create mode 100644 docs/source/api_docs.rst create mode 100644 docs/source/c_api/neighbors.rst create mode 100644 docs/source/python_api/neighbors.rst create mode 100644 docs/source/python_api/neighbors_cagra.rst create mode 100644 docs/source/rust_api.rst rename {cpp/examples => examples/cpp}/CMakeLists.txt (100%) rename {cpp/examples => examples/cpp}/README.md (93%) rename {cpp/examples => examples/cpp}/build.sh (100%) rename {cpp/examples => examples/cpp}/cmake/thirdparty/fetch_rapids.cmake (100%) rename {cpp/examples => examples/cpp}/cmake/thirdparty/get_cuvs.cmake (100%) rename {cpp/examples => examples/cpp}/src/cagra_example.cu (100%) rename {cpp/examples => examples/cpp}/src/common.cuh (100%) diff --git a/build.sh b/build.sh index d53f7c3545..8571a574c1 100755 --- a/build.sh +++ b/build.sh @@ -403,7 +403,7 @@ fi # Initiate build for c++ examples (if needed) if hasArg examples; then - pushd ${REPODIR}/cpp/examples + pushd ${REPODIR}/examples/cpp ./build.sh popd fi diff --git a/cpp/doxygen/Doxyfile b/cpp/doxygen/Doxyfile index 94304afe02..faa31c25d0 100644 --- a/cpp/doxygen/Doxyfile +++ b/cpp/doxygen/Doxyfile @@ -864,6 +864,7 @@ INPUT_ENCODING = UTF-8 # *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.hpp \ + *.h \ *.cuh # The RECURSIVE tag can be used to specify whether or not subdirectories should diff --git a/cpp/include/cuvs/core/c_api.h b/cpp/include/cuvs/core/c_api.h index eceb917ff3..a15d7cd5c7 100644 --- a/cpp/include/cuvs/core/c_api.h +++ b/cpp/include/cuvs/core/c_api.h @@ -16,30 +16,46 @@ #pragma once +#include #include -#include +#ifdef __cplusplus +extern "C" { +#endif /** - * @defgroup c_api C API Core Types and Functions + * @defgroup error_c cuVS Error Messages * @{ */ +/** + * @brief An enum denoting return values for function calls + * + */ +typedef enum { CUVS_ERROR, CUVS_SUCCESS } cuvsError_t; -#ifdef __cplusplus -extern "C" { -#endif +/** @brief Returns a string describing the last seen error on this thread, or + * NULL if the last function succeeded. + */ +const char* cuvsGetLastErrorText(); /** - * @brief An opaque C handle for C++ type `raft::resources` - * + * @brief Sets a string describing an error seen on the thread. Passing NULL + * clears any previously seen error message. */ -typedef uintptr_t cuvsResources_t; +void cuvsSetLastErrorText(const char* error); + +/** @} */ /** - * @brief An enum denoting return values for function calls + * @defgroup resources_c cuVS Resources Handle + * @{ + */ + +/** + * @brief An opaque C handle for C++ type `raft::resources` * */ -typedef enum { CUVS_ERROR, CUVS_SUCCESS } cuvsError_t; +typedef uintptr_t cuvsResources_t; /** * @brief Create an Initialized opaque C handle for C++ type `raft::resources` @@ -67,18 +83,8 @@ cuvsError_t cuvsResourcesDestroy(cuvsResources_t res); */ cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream); -/** @brief Returns a string describing the last seen error on this thread, or - * NULL if the last function succeeded. - */ -const char* cuvsGetLastErrorText(); +/** @} */ -/** - * @brief Sets a string describing an error seen on the thread. Passing NULL - * clears any previously seen error message. - */ -void cuvsSetLastErrorText(const char* error); #ifdef __cplusplus } #endif - -/** @} */ diff --git a/cpp/include/cuvs/neighbors/cagra.h b/cpp/include/cuvs/neighbors/cagra.h index 64a26b9243..ae8f8ea01d 100644 --- a/cpp/include/cuvs/neighbors/cagra.h +++ b/cpp/include/cuvs/neighbors/cagra.h @@ -20,15 +20,15 @@ #include #include -/** - * @defgroup cagra_c C API for CUDA ANN Graph-based nearest neighbor search - * @{ - */ - #ifdef __cplusplus extern "C" { #endif +/** + * @defgroup cagra_c_index_params C API for CUDA ANN Graph-based nearest neighbor search + * @{ + */ + /** * @brief Enum to denote which ANN algorithm is used to build CAGRA graph * @@ -71,7 +71,16 @@ cuvsError_t cuvsCagraIndexParamsCreate(cuvsCagraIndexParams_t* params); * @param[in] params * @return cuvsError_t */ -cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t index); +cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t params); + +/** + * @} + */ + +/** + * @defgroup cagra_c_search_params C API for CUDA ANN Graph-based nearest neighbor search + * @{ + */ /** * @brief Enum to denote algorithm used to search CAGRA Index @@ -158,6 +167,15 @@ cuvsError_t cuvsCagraSearchParamsCreate(cuvsCagraSearchParams_t* params); */ cuvsError_t cuvsCagraSearchParamsDestroy(cuvsCagraSearchParams_t params); +/** + * @} + */ + +/** + * @defgroup cagra_c_index C API for CUDA ANN Graph-based nearest neighbor search + * @{ + */ + /** * @brief Struct to hold address of cuvs::neighbors::cagra::index and its active trained dtype * @@ -185,6 +203,15 @@ cuvsError_t cuvsCagraIndexCreate(cuvsCagraIndex_t* index); */ cuvsError_t cuvsCagraIndexDestroy(cuvsCagraIndex_t index); +/** + * @} + */ + +/** + * @defgroup cagra_c_index_build C API for CUDA ANN Graph-based nearest neighbor search + * @{ + */ + /** * @brief Build a CAGRA index with a `DLManagedTensor` which has underlying * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`, @@ -232,6 +259,14 @@ cuvsError_t cuvsCagraBuild(cuvsResources_t res, DLManagedTensor* dataset, cuvsCagraIndex_t index); +/** + * @} + */ + +/** + * @defgroup cagra_c_index_search C API for CUDA ANN Graph-based nearest neighbor search + * @{ + */ /** * @brief Search a CAGRA index with a `DLManagedTensor` which has underlying * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`. @@ -281,10 +316,10 @@ cuvsError_t cuvsCagraSearch(cuvsResources_t res, DLManagedTensor* neighbors, DLManagedTensor* distances); -#ifdef __cplusplus -} -#endif - /** * @} */ + +#ifdef __cplusplus +} +#endif diff --git a/cpp/include/cuvs/neighbors/cagra.hpp b/cpp/include/cuvs/neighbors/cagra.hpp index 3a0c60b782..0f2919ff46 100644 --- a/cpp/include/cuvs/neighbors/cagra.hpp +++ b/cpp/include/cuvs/neighbors/cagra.hpp @@ -27,7 +27,7 @@ namespace cuvs::neighbors::cagra { /** - * @addtogroup cagra + * @defgroup cagra_cpp_index_params CAGRA index build parameters * @{ */ @@ -68,6 +68,15 @@ struct index_params : ann::index_params { } }; +/** + * @} + */ + +/** + * @defgroup cagra_cpp_search_params CAGRA index search parameters + * @{ + */ + enum class search_algo { /** For large batch sizes. */ SINGLE_CTA, @@ -144,9 +153,17 @@ struct search_params : ann::search_params { } }; +/** + * @} + */ static_assert(std::is_aggregate_v); static_assert(std::is_aggregate_v); +/** + * @defgroup cagra_cpp_index CAGRA index type + * @{ + */ + /** * @brief CAGRA index. * @@ -344,70 +361,162 @@ struct index : ann::index { std::unique_ptr> raft_index_; }; -// Using device and host_matrix_view avoids needing to typedef multiple mdspans based on accessors -#define CUVS_INST_CAGRA_FUNCS(T, IdxT) \ - auto build(raft::resources const& handle, \ - const cuvs::neighbors::cagra::index_params& params, \ - raft::device_matrix_view dataset) \ - ->cuvs::neighbors::cagra::index; \ - \ - auto build(raft::resources const& handle, \ - const cuvs::neighbors::cagra::index_params& params, \ - raft::host_matrix_view dataset) \ - ->cuvs::neighbors::cagra::index; \ - \ - void build_device(raft::resources const& handle, \ - const cuvs::neighbors::cagra::index_params& params, \ - raft::device_matrix_view dataset, \ - cuvs::neighbors::cagra::index& idx); \ - \ - void build_host(raft::resources const& handle, \ - const cuvs::neighbors::cagra::index_params& params, \ - raft::host_matrix_view dataset, \ - cuvs::neighbors::cagra::index& idx); \ - \ - void search(raft::resources const& handle, \ - cuvs::neighbors::cagra::search_params const& params, \ - const cuvs::neighbors::cagra::index& index, \ - raft::device_matrix_view queries, \ - raft::device_matrix_view neighbors, \ - raft::device_matrix_view distances); \ - void serialize_file(raft::resources const& handle, \ - const std::string& filename, \ - const cuvs::neighbors::cagra::index& index, \ - bool include_dataset = true); \ - \ - void deserialize_file(raft::resources const& handle, \ - const std::string& filename, \ - cuvs::neighbors::cagra::index* index); \ - void serialize(raft::resources const& handle, \ - std::string& str, \ - const cuvs::neighbors::cagra::index& index, \ - bool include_dataset = true); \ - \ - void deserialize(raft::resources const& handle, \ - const std::string& str, \ - cuvs::neighbors::cagra::index* index); - -CUVS_INST_CAGRA_FUNCS(float, uint32_t); -CUVS_INST_CAGRA_FUNCS(int8_t, uint32_t); -CUVS_INST_CAGRA_FUNCS(uint8_t, uint32_t); - -#undef CUVS_INST_CAGRA_FUNCS - -#define CUVS_INST_CAGRA_OPTIMIZE(IdxT) \ - void optimize_device(raft::resources const& res, \ - raft::device_matrix_view knn_graph, \ - raft::host_matrix_view new_graph); \ - \ - void optimize_host(raft::resources const& res, \ - raft::host_matrix_view knn_graph, \ - raft::host_matrix_view new_graph); - -CUVS_INST_CAGRA_OPTIMIZE(uint32_t); - -#undef CUVS_INST_CAGRA_OPTIMIZE - -/** @} */ +/** + * @} + */ + +/** + * @defgroup cagra_cpp_index_build CAGRA index build functions + * @{ + */ +auto build(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::cagra::index; + +auto build(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::host_matrix_view dataset) + -> cuvs::neighbors::cagra::index; + +auto build(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::cagra::index; + +auto build(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::host_matrix_view dataset) + -> cuvs::neighbors::cagra::index; + +auto build(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::cagra::index; + +auto build(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::host_matrix_view dataset) + -> cuvs::neighbors::cagra::index; +/** + * @} + */ + +void build_device(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::device_matrix_view dataset, + cuvs::neighbors::cagra::index& idx); + +void build_host(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::host_matrix_view dataset, + cuvs::neighbors::cagra::index& idx); + +void build_device(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::device_matrix_view dataset, + cuvs::neighbors::cagra::index& idx); + +void build_host(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::host_matrix_view dataset, + cuvs::neighbors::cagra::index& idx); + +void build_device(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::device_matrix_view dataset, + cuvs::neighbors::cagra::index& idx); + +void build_host(raft::resources const& handle, + const cuvs::neighbors::cagra::index_params& params, + raft::host_matrix_view dataset, + cuvs::neighbors::cagra::index& idx); + +/** + * @defgroup cagra_cpp_index_search CAGRA search functions + * @{ + */ + +void search(raft::resources const& handle, + cuvs::neighbors::cagra::search_params const& params, + const cuvs::neighbors::cagra::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); + +void search(raft::resources const& handle, + cuvs::neighbors::cagra::search_params const& params, + const cuvs::neighbors::cagra::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); + +void search(raft::resources const& handle, + cuvs::neighbors::cagra::search_params const& params, + const cuvs::neighbors::cagra::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); +/** + * @} + */ + +/** + * @defgroup cagra_cpp_serialize CAGRA serialize functions + * @{ + */ +void serialize_file(raft::resources const& handle, + const std::string& filename, + const cuvs::neighbors::cagra::index& index, + bool include_dataset = true); + +void deserialize_file(raft::resources const& handle, + const std::string& filename, + cuvs::neighbors::cagra::index* index); +void serialize(raft::resources const& handle, + std::string& str, + const cuvs::neighbors::cagra::index& index, + bool include_dataset = true); + +void deserialize(raft::resources const& handle, + const std::string& str, + cuvs::neighbors::cagra::index* index); + +void serialize_file(raft::resources const& handle, + const std::string& filename, + const cuvs::neighbors::cagra::index& index, + bool include_dataset = true); + +void deserialize_file(raft::resources const& handle, + const std::string& filename, + cuvs::neighbors::cagra::index* index); +void serialize(raft::resources const& handle, + std::string& str, + const cuvs::neighbors::cagra::index& index, + bool include_dataset = true); + +void deserialize(raft::resources const& handle, + const std::string& str, + cuvs::neighbors::cagra::index* index); + +void serialize_file(raft::resources const& handle, + const std::string& filename, + const cuvs::neighbors::cagra::index& index, + bool include_dataset = true); + +void deserialize_file(raft::resources const& handle, + const std::string& filename, + cuvs::neighbors::cagra::index* index); +void serialize(raft::resources const& handle, + std::string& str, + const cuvs::neighbors::cagra::index& index, + bool include_dataset = true); + +void deserialize(raft::resources const& handle, + const std::string& str, + cuvs::neighbors::cagra::index* index); +/** + * @} + */ } // namespace cuvs::neighbors::cagra diff --git a/docs/source/api_docs.rst b/docs/source/api_docs.rst new file mode 100644 index 0000000000..2a5a867e0a --- /dev/null +++ b/docs/source/api_docs.rst @@ -0,0 +1,11 @@ +API Documentation +================= + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + c_api.rst + cpp_api.rst + python_api.rst + rust_api.rst diff --git a/docs/source/build.md b/docs/source/build.md index 31de69b461..939f1db346 100644 --- a/docs/source/build.md +++ b/docs/source/build.md @@ -1,318 +1,184 @@ -# Installation +**# Installation -RAFT currently provides libraries for C++ and Python. The C++ libraries, including the header-only and optional shared library, can be installed with Conda. +The cuVS software development kit provides APIs for C, C++, Python, and Rust languages. These Both the C++ and Python APIs require CMake to build from source. ## Table of Contents -- [Install C++ and Python through Conda](#installing-c-and-python-through-conda) -- [Installing Python through Pip](#installing-python-through-pip) -- [Building C++ and Python from source](#building-c-and-python-from-source) - - [CUDA/GPU requirements](#cudagpu-requirements) - - [Build dependencies](#build-dependencies) - - [Required](#required) - - [Optional](#optional) - - [Conda environment scripts](#conda-environment-scripts) - - [Header-only C++](#header-only-c) - - [C++ shared library](#c-shared-library-optional) - - [ccache and sccache](#ccache-and-sccache) - - [C++ tests](#c-tests) - - [C++ primitives microbenchmarks](#c-primitives-microbenchmarks) - - [Python libraries](#python-libraries) -- [Using CMake directly](#using-cmake-directly) -- [Build documentation](#build-documentation) -- [Using RAFT in downstream projects](#using-raft-c-in-downstream-projects) - - [CMake targets](#cmake-targets) +- [Install pre-compiled packages](#installing-precompiled-packages) + - [C, C++, and Python through Conda](#installing-through-conda) + - [Python through Pip](#installing-python-through-pip) + - [Rust through crates.io](#installing-rust-through-crates) +- [Build from source](#building-c-and-python-from-source) + - [Prerequisites](#build-prerequisites) + - [CUDA/GPU requirements](#cudagpu-requirements) + - [Build dependencies](#build-dependencies) + - [Required](#required) + - [Create a build environment](#creating-a-build-environment) + - [ccache and sccache](#ccache-and-sccache) + - [C/C++ Libraries](#c-library) + - [Building the Googletests](#building-the-googletests) + - [Python Library](#python-library) + - [Rust Library](#rust-library) + - [Using Cmake Directly](#using-cmake-directly) +- [Build Documentation](#build-documentation) +- [Use cuVS in your application](#use-cuvs-in-your-application) + +[//]: # (- [Using cuVS in downstream projects](#using-raft-c-in-downstream-projects)) + +[//]: # ( - [CMake targets](#cmake-targets)_) ------ -## Installing C++ and Python through Conda +## Installing Pre-compiled Packages -The easiest way to install RAFT is through conda and several packages are provided. -- `libraft-headers` C++ headers -- `libraft` (optional) C++ shared library containing pre-compiled template instantiations and runtime API. -- `pylibraft` (optional) Python library -- `raft-dask` (optional) Python library for deployment of multi-node multi-GPU algorithms that use the RAFT `raft::comms` abstraction layer in Dask clusters. -- `raft-ann-bench` (optional) Benchmarking tool for easily producing benchmarks that compare RAFT's vector search algorithms against other state-of-the-art implementations. -- `raft-ann-bench-cpu` (optional) Reproducible benchmarking tool similar to above, but doesn't require CUDA to be installed on the machine. Can be used to test in environments with competitive CPUs. +### C, C++, and Python through Conda -Use the following command, depending on your CUDA version, to install all of the RAFT packages with conda (replace `rapidsai` with `rapidsai-nightly` to install more up-to-date but less stable nightly packages). `mamba` is preferred over the `conda` command. +The easiest way to install the pre-compiled C, C++, and Python packages is through [conda](https://docs.anaconda.com/free/miniconda/index.html). Use the following commands, depending on your CUDA version, to install cuVS packages (replace `rapidsai` with `rapidsai-nightly` to install more up-to-date but less stable nightly packages). `mamba` is preferred over the `conda` command. + +#### C++ Package ```bash -# for CUDA 11.8 -mamba install -c rapidsai -c conda-forge -c nvidia raft-dask pylibraft cuda-version=11.8 +mamba install -c rapidsai -c conda-forge -c nvidia libcuvs cuda-version=11.8 ``` +#### C Package ```bash -# for CUDA 12.0 -mamba install -c rapidsai -c conda-forge -c nvidia raft-dask pylibraft cuda-version=12.0 +mamba install -c rapidsai -c conda-forge -c nvidia libcuvs_c cuda-version=11.8 ``` -Note that the above commands will also install `libraft-headers` and `libraft`. - -You can also install the conda packages individually using the `mamba` command above. For example, if you'd like to install RAFT's headers and pre-compiled shared library to use in your project: +#### Python Package ```bash -# for CUDA 12.0 -mamba install -c rapidsai -c conda-forge -c nvidia libraft libraft-headers cuda-version=12.0 +mamba install -c rapidsai -c conda-forge -c nvidia pycuvs cuda-version=11.8 ``` -If installing the C++ APIs Please see [using libraft](https://docs.rapids.ai/api/raft/nightly/using_libraft/) for more information on using the pre-compiled shared library. You can also refer to the [example C++ template project](https://github.com/rapidsai/raft/tree/branch-24.04/cpp/template) for a ready-to-go CMake configuration that you can drop into your project and build against installed RAFT development artifacts above. +### Python through Pip -## Installing Python through Pip - -`pylibraft` and `raft-dask` both have packages that can be [installed through pip](https://rapids.ai/pip.html#install). +The cuVS Python package can also be [installed through pip](https://rapids.ai/pip.html#install). For CUDA 11 packages: ```bash -pip install pylibraft-cu11 --extra-index-url=https://pypi.nvidia.com -pip install raft-dask-cu11 --extra-index-url=https://pypi.nvidia.com +pip install pycuvs-cu11 --extra-index-url=https://pypi.nvidia.com ``` And CUDA 12 packages: ```bash -pip install pylibraft-cu12 --extra-index-url=https://pypi.nvidia.com -pip install raft-dask-cu12 --extra-index-url=https://pypi.nvidia.com +pip install pycuvs-cu12 --extra-index-url=https://pypi.nvidia.com ``` -These packages statically build RAFT's pre-compiled instantiations, so the C++ headers and pre-compiled shared library won't be readily available to use in your code. +Note: these packages statically links the C and C++ libraries so the `libcuvs` and `libcuvs_c` shared libraries won't be readily available to use in your code. -## Building C++ and Python from source +### Rust through crates.io -### CUDA/GPU Requirements -- cmake 3.26.4+ -- GCC 9.3+ (9.5.0+ recommended) -- CUDA Toolkit 11.2+ -- NVIDIA driver 450.80.02+ -- Pascal architecture or better (compute capability >= 6.0) +## Build from source -### Build Dependencies +The core cuVS source code is written in C++ and wrapped through a C API. The C API is wrapped around the C++ APIs and the other supported languages are built around the C API. -In addition to the libraries included with cudatoolkit 11.0+, there are some other dependencies below for building RAFT from source. Many of the dependencies are optional and depend only on the primitives being used. All of these can be installed with cmake or [rapids-cpm](https://github.com/rapidsai/rapids-cmake#cpm) and many of them can be installed with [conda](https://anaconda.org). -#### Required -- [RMM](https://github.com/rapidsai/rmm) corresponding to RAFT version. -- [Thrust](https://github.com/NVIDIA/thrust) v1.17 / [CUB](https://github.com/NVIDIA/cub) -- [cuCollections](https://github.com/NVIDIA/cuCollections) - Used in `raft::sparse::distance` API. -- [CUTLASS](https://github.com/NVIDIA/cutlass) v2.9.1 - Used in `raft::distance` API. +### Prerequisites -#### Optional -- [NCCL](https://github.com/NVIDIA/nccl) - Used in `raft::comms` API and needed to build `raft-dask`. -- [UCX](https://github.com/openucx/ucx) - Used in `raft::comms` API and needed to build `raft-dask`. -- [Googletest](https://github.com/google/googletest) - Needed to build tests -- [Googlebench](https://github.com/google/benchmark) - Needed to build benchmarks -- [Doxygen](https://github.com/doxygen/doxygen) - Needed to build docs +- Cmake 3.26.4+ +- GCC 9.3+ (11.4+ recommended) +- CUDA Toolkit 11.4+ +- Volta architecture or better (compute capability >= 7.0) -#### Conda environment scripts +### Create a build environment -Conda environment scripts are provided for installing the necessary dependencies to build both the C++ and Python libraries from source. It is preferred to use `mamba`, as it provides significant speedup over `conda`: +[Conda](https://docs.anaconda.com/free/miniconda/index.html) environment scripts are provided for installing the necessary dependencies to build cuVS from source. It is preferred to use `mamba`, as it provides significant speedup over `conda`: ```bash -mamba env create --name rapids_raft -f conda/environments/all_cuda-120_arch-x86_64.yaml -mamba activate rapids_raft +mamba env create --name cuvs -f conda/environments/all_cuda-120_arch-x86_64.yaml +mamba activate cuvs ``` -All of RAFT's C++ APIs can be used header-only and optional pre-compiled shared libraries provide some host-accessible runtime APIs and template instantiations to accelerate compile times. - The process for building from source with CUDA 11 differs slightly in that your host system will also need to have CUDA toolkit installed which is greater than, or equal to, the version you install into you conda environment. Installing CUDA toolkit into your host system is necessary because `nvcc` is not provided with Conda's cudatoolkit dependencies for CUDA 11. The following example will install create and install dependencies for a CUDA 11.8 conda environment ```bash -mamba env create --name rapids_raft -f conda/environments/all_cuda-118_arch-x86_64.yaml -mamba activate rapids_raft +mamba env create --name cuvs -f conda/environments/all_cuda-118_arch-x86_64.yaml +mamba activate cuvs ``` -The recommended way to build and install RAFT from source is to use the `build.sh` script in the root of the repository. This script can build both the C++ and Python artifacts and provides CMake options for building and installing the headers, tests, benchmarks, and the pre-compiled shared library. - -### Header-only C++ - -`build.sh` uses [rapids-cmake](https://github.com/rapidsai/rapids-cmake), which will automatically download any dependencies which are not already installed. It's important to note that while all the headers will be installed and available, some parts of the RAFT API depend on libraries like CUTLASS, which will need to be explicitly enabled in `build.sh`. - -The following example will download the needed dependencies and install the RAFT headers into `$INSTALL_PREFIX/include/raft`. -```bash -./build.sh libraft -``` -The `-n` flag can be passed to just have the build download the needed dependencies. Since RAFT's C++ headers are primarily used during build-time in downstream projects, the dependencies will never be installed by the RAFT build. -```bash -./build.sh libraft -n -``` +The recommended way to build and install cuVS from source is to use the `build.sh` script in the root of the repository. This script can build both the C++ and Python artifacts and provides CMake options for building and installing the headers, tests, benchmarks, and the pre-compiled shared library. -Once installed, `libraft` headers (and dependencies which were downloaded and installed using `rapids-cmake`) can be uninstalled also using `build.sh`: -```bash -./build.sh libraft --uninstall -``` -### C++ Shared Library (optional) +### C and C++ libraries -A shared library can be built for speeding up compile times. The shared library also contains a runtime API that allows you to invoke RAFT APIs directly from C++ source files (without `nvcc`). The shared library can also significantly improve re-compile times both while developing RAFT and using its APIs to develop applications. Pass the `--compile-lib` flag to `build.sh` to build the library: +The C and C++ shared libraries are built together using the following arguments to `build.sh`: ```bash -./build.sh libraft --compile-lib +./build.sh libcuvs ``` -In above example the shared library is installed by default into `$INSTALL_PREFIX/lib`. To disable this, pass `-n` flag. +In above example the `libcuvs.so` and `libcuvs_c.so` shared libraries are installed by default into `$INSTALL_PREFIX/lib`. To disable this, pass `-n` flag. -Once installed, the shared library, headers (and any dependencies downloaded and installed via `rapids-cmake`) can be uninstalled using `build.sh`: +Once installed, the shared libraries, headers (and any dependencies downloaded and installed via `rapids-cmake`) can be uninstalled using `build.sh`: ```bash -./build.sh libraft --uninstall +./build.sh libcuvs --uninstall ``` -### ccache and sccache +#### Building the Googletests -`ccache` and `sccache` can be used to better cache parts of the build when rebuilding frequently, such as when working on a new feature. You can also use `ccache` or `sccache` with `build.sh`: +Compile the C and C++ Googletests using the `tests` target in `build.sh`. ```bash -./build.sh libraft --cache-tool=ccache +./build.sh libcuvs tests ``` -### C++ Tests +The tests will be written to the build directory, which is `cpp/build/` by default, and they will be named `*_TEST`. -Compile the tests using the `tests` target in `build.sh`. +It can take sometime to compile all of the tests. You can build individual tests by providing a semicolon-separated list to the `--limit-tests` option in `build.sh`. Make sure to pass the `-n` flag so the tests are not installed. ```bash -./build.sh libraft tests +./build.sh libcuvs tests -n --limit-tests=NEIGHBORS_TEST;CLUSTER_TEST ``` -Test compile times can be improved significantly by using the optional shared libraries. If installed, they will be used automatically when building the tests but `--compile-libs` can be used to add additional compilation units and compile them with the tests. +### Python library -```bash -./build.sh libraft tests --compile-lib -``` - -The tests are broken apart by algorithm category, so you will find several binaries in `cpp/build/` named `*_TEST`. +The Python library should be built and installed using the `build.sh` script: -For example, to run the distance tests: ```bash -./cpp/build/DISTANCE_TEST +./build.sh python ``` -It can take sometime to compile all of the tests. You can build individual tests by providing a semicolon-separated list to the `--limit-tests` option in `build.sh`: - -```bash -./build.sh libraft tests -n --limit-tests=NEIGHBORS_TEST;DISTANCE_TEST;MATRIX_TEST -``` - -### C++ Primitives Microbenchmarks - -The benchmarks are broken apart by algorithm category, so you will find several binaries in `cpp/build/` named `*_PRIMS_BENCH`. -```bash -./build.sh libraft bench-prims -``` - -It can take sometime to compile all of the benchmarks. You can build individual benchmarks by providing a semicolon-separated list to the `--limit-bench-prims` option in `build.sh`: - -```bash -./build.sh libraft bench-prims -n --limit-bench=NEIGHBORS_PRIMS_BENCH;DISTANCE_PRIMS_BENCH;LINALG_PRIMS_BENCH -``` - -In addition to microbenchmarks for individual primitives, RAFT contains a reproducible benchmarking tool for evaluating the performance of RAFT's vector search algorithms against the existing state-of-the-art. Please refer to the [RAFT ANN Benchmarks](https://docs.rapids.ai/api/raft/nightly/raft_ann_benchmarks/) guide for more information on this tool. - -### Python libraries - -The Python libraries can be built and installed using the `build.sh` script: - -```bash -# to build pylibraft -./build.sh libraft pylibraft --compile-lib -# to build raft-dask (depends on pylibraft) -./build.sh libraft pylibraft raft-dask --compile-lib -``` - -`setup.py` can also be used to build the Python libraries manually: - +The Python packages can also be uninstalled using the `build.sh` script: ```bash -cd python/raft-dask -python setup.py build_ext --inplace -python setup.py install - -cd python/pylibraft -python setup.py build_ext --inplace -python setup.py install +./build.sh python --uninstall ``` -Python tests are automatically installed with the corresponding libraries. To run Python tests: -```bash -cd python/raft-dask -py.test -s -v +### Rust library -cd python/pylibraft -py.test -s -v -``` -The Python packages can also be uninstalled using the `build.sh` script: -```bash -./build.sh pylibraft raft-dask --uninstall -``` ### Using CMake directly -When building RAFT from source, the `build.sh` script offers a nice wrapper around the `cmake` commands to ease the burdens of manually configuring the various available cmake options. When more fine-grained control over the CMake configuration is desired, the `cmake` command can be invoked directly as the below example demonstrates. +When building cuVS from source, the `build.sh` script offers a nice wrapper around the `cmake` commands to ease the burdens of manually configuring the various available cmake options. When more fine-grained control over the CMake configuration is desired, the `cmake` command can be invoked directly as the below example demonstrates. -The `CMAKE_INSTALL_PREFIX` installs RAFT into a specific location. The example below installs RAFT into the current Conda environment: +The `CMAKE_INSTALL_PREFIX` installs cuVS into a specific location. The example below installs cuVS into the current Conda environment: ```bash cd cpp mkdir build cd build -cmake -D BUILD_TESTS=ON -DRAFT_COMPILE_LIBRARY=ON -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX ../ +cmake -D BUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX ../ make -j install ``` -RAFT's CMake has the following configurable flags available: - -| Flag | Possible Values | Default Value | Behavior | -|---------------------------------|----------------------| --- |------------------------------------------------------------------------------| -| BUILD_TESTS | ON, OFF | ON | Compile Googletests | -| BUILD_PRIMS_BENCH | ON, OFF | OFF | Compile benchmarks | -| BUILD_ANN_BENCH | ON, OFF | OFF | Compile end-to-end ANN benchmarks | -| CUDA_ENABLE_KERNELINFO | ON, OFF | OFF | Enables `kernelinfo` in nvcc. This is useful for `compute-sanitizer` | -| CUDA_ENABLE_LINEINFO | ON, OFF | OFF | Enable the -lineinfo option for nvcc | -| CUDA_STATIC_RUNTIME | ON, OFF | OFF | Statically link the CUDA runtime | -| DETECT_CONDA_ENV | ON, OFF | ON | Enable detection of conda environment for dependencies | -| raft_FIND_COMPONENTS | compiled distributed | | Configures the optional components as a space-separated list | -| RAFT_COMPILE_LIBRARY | ON, OFF | ON if either BUILD_TESTS or BUILD_PRIMS_BENCH is ON; otherwise OFF | Compiles all `libraft` shared libraries (these are required for Googletests) | -| RAFT_ENABLE_CUBLAS_DEPENDENCY | ON, OFF | ON | Link against cublas library in `raft::raft` | -| RAFT_ENABLE_CUSOLVER_DEPENDENCY | ON, OFF | ON | Link against cusolver library in `raft::raft` | -| RAFT_ENABLE_CUSPARSE_DEPENDENCY | ON, OFF | ON | Link against cusparse library in `raft::raft` | -| RAFT_ENABLE_CUSOLVER_DEPENDENCY | ON, OFF | ON | Link against curand library in `raft::raft` | -| RAFT_NVTX | ON, OFF | OFF | Enable NVTX Markers | - -### Build documentation +cuVS has the following configurable cmake flags available: -The documentation requires that the C++ and Python libraries have been built and installed. The following will build the docs along with the C++ and Python packages: - -``` -./build.sh libraft pylibraft raft-dask docs --compile-lib -``` +| Flag | Possible Values | Default Value | Behavior | +|------------------------|----------------------| --- |------------------------------------------------------------------------------| +| BUILD_TESTS | ON, OFF | ON | Compile Googletests | +| CUDA_ENABLE_KERNELINFO | ON, OFF | OFF | Enables `kernelinfo` in nvcc. This is useful for `compute-sanitizer` | +| CUDA_ENABLE_LINEINFO | ON, OFF | OFF | Enable the -lineinfo option for nvcc | +| CUDA_STATIC_RUNTIME | ON, OFF | OFF | Statically link the CUDA runtime | +| DETECT_CONDA_ENV | ON, OFF | ON | Enable detection of conda environment for dependencies | +| CUVS_NVTX | ON, OFF | OFF | Enable NVTX Markers | -## Using RAFT C++ in downstream projects - -There are a few different strategies for including RAFT in downstream projects, depending on whether the [required build dependencies](#build-dependencies) have already been installed and are available on the `lib` and `include` search paths. +### Build documentation -When using the GPU parts of RAFT, you will need to enable CUDA support in your CMake project declaration: -```cmake -project(YOUR_PROJECT VERSION 0.1 LANGUAGES CXX CUDA) -``` +The documentation requires that the C, C++ and Python libraries have been built and installed. The following will build the docs along with the necessary libraries: -Note that some additional compiler flags might need to be added when building against RAFT. For example, if you see an error like this `The experimental flag '--expt-relaxed-constexpr' can be used to allow this.`. The necessary flags can be set with CMake: -```cmake -target_compile_options(your_target_name PRIVATE $<$:--expt-extended-lambda --expt-relaxed-constexpr>) ``` - -Further, it's important that the language level be set to at least C++ 17. This can be done with cmake: -```cmake -set_target_properties(your_target_name -PROPERTIES CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON - CUDA_STANDARD 17 - CUDA_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON - INTERFACE_POSITION_INDEPENDENT_CODE ON) +./build.sh libcuvs python docs ``` -The [C++ example template project](https://github.com/rapidsai/raft/tree/HEAD/cpp/template) provides an end-to-end buildable example of what a `CMakeLists.txt` that uses RAFT should look like. The items below point out some of the needed details. - -#### CMake Targets - -The `raft::raft` CMake target is made available when including RAFT into your CMake project but additional CMake targets can be made available by adding to the `COMPONENTS` option in CMake's `find_package(raft)` (refer to [CMake docs](https://cmake.org/cmake/help/latest/command/find_package.html#basic-signature) to learn more). The components should be separated by spaces. The `raft::raft` target will always be available. Note that the `distributed` component also exports additional dependencies. +## Use cuVS in your application -| Component | Target | Description | Base Dependencies | -|-------------|---------------------|----------------------------------------------------------|----------------------------------------| -| n/a | `raft::raft` | Full RAFT header library | CUDA toolkit, RMM, NVTX, CCCL, CUTLASS | -| compiled | `raft::compiled` | Pre-compiled template instantiations and runtime library | raft::raft | -| distributed | `raft::distributed` | Dependencies for `raft::comms` APIs | raft::raft, UCX, NCCL +The [examples/](https://github.com/rapidsai/raft/tree/HEAD/examples) directory at the root of the cuVS repository has self-contains drop-in projects to build and use the cuVS SDK in your applications. \ No newline at end of file diff --git a/docs/source/c_api.rst b/docs/source/c_api.rst index 4daaa84dfd..3359a5dbec 100644 --- a/docs/source/c_api.rst +++ b/docs/source/c_api.rst @@ -1,6 +1,6 @@ -~~~~~ -C API -~~~~~ +~~~~~~~~~~~~~~~~~~~ +C API Documentation +~~~~~~~~~~~~~~~~~~~ .. _api: @@ -8,4 +8,4 @@ C API :maxdepth: 4 c_api/core_c_api.rst - c_api/neighbors_cagra_c.rst + c_api/neighbors.rst diff --git a/docs/source/c_api/core_c_api.rst b/docs/source/c_api/core_c_api.rst index e0060bd5d4..e2796700fd 100644 --- a/docs/source/c_api/core_c_api.rst +++ b/docs/source/c_api/core_c_api.rst @@ -1,14 +1,24 @@ -C-API -===== +Core Routines +============= .. role:: py(code) :language: c :class: highlight - ``#include `` -.. doxygengroup:: c_api - :project: RAFT +Resources Handle +---------------- + +.. doxygengroup:: resources_c + :project: cuvs + :members: + :content-only: + +Error Handling +-------------- + +.. doxygengroup:: error_c + :project: cuvs :members: :content-only: diff --git a/docs/source/c_api/neighbors.rst b/docs/source/c_api/neighbors.rst new file mode 100644 index 0000000000..6bbd3cc7f3 --- /dev/null +++ b/docs/source/c_api/neighbors.rst @@ -0,0 +1,14 @@ +Nearest Neighbors +================= + +.. role:: py(code) + :language: c + :class: highlight + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + + neighbors_cagra_c.rst \ No newline at end of file diff --git a/docs/source/c_api/neighbors_cagra_c.rst b/docs/source/c_api/neighbors_cagra_c.rst index d4b7b0bf27..eb40d55782 100644 --- a/docs/source/c_api/neighbors_cagra_c.rst +++ b/docs/source/c_api/neighbors_cagra_c.rst @@ -1,7 +1,8 @@ -CAGRA C-API -=========== +CAGRA +===== + +CAGRA is a graph-based nearest neighbors algorithm that was built from the ground up for GPU acceleration. CAGRA demonstrates state-of-the art index build and query performance for both small- and large-batch sized search. -CAGRA is a graph-based nearest neighbors implementation with state-of-the art query performance for both small- and large-batch sized search. .. role:: py(code) :language: c @@ -9,7 +10,44 @@ CAGRA is a graph-based nearest neighbors implementation with state-of-the art qu ``#include `` -.. doxygengroup:: cagra_c - :project: RAFT +Index build parameters +---------------------- + +.. doxygengroup:: cagra_c_index_params + :project: cuvs + :members: + :content-only: + +Index search parameters +----------------------- + +.. doxygengroup:: cagra_c_search_params + :project: cuvs + :members: + :content-only: + +Index +----- + +.. doxygengroup:: cagra_c_index + :project: cuvs :members: :content-only: + +Index build +----------- + +.. doxygengroup:: cagra_c_index_build + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: cagra_c_index_search + :project: cuvs + :members: + :content-only: + + diff --git a/docs/source/conf.py b/docs/source/conf.py index 1a5c9dfe83..1089f6a5ca 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,11 +23,11 @@ # ones. extensions = [ "numpydoc", + "sphinx.ext.linkcode", "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.doctest", "sphinx.ext.intersphinx", - "sphinx.ext.linkcode", "IPython.sphinxext.ipython_console_highlighting", "IPython.sphinxext.ipython_directive", "breathe", @@ -59,7 +59,7 @@ # General information about the project. project = "cuvs" -copyright = "2023, NVIDIA Corporation" +copyright = "2024, NVIDIA Corporation" author = "NVIDIA Corporation" # The version info for the project you're documenting, acts as replacement for @@ -193,8 +193,8 @@ def setup(app): # The following is used by sphinx.ext.linkcode to provide links to github linkcode_resolve = make_linkcode_resolve( "cuvs", - "https://github.com/rapidsai/cuvs" - "cuvs/blob/{revision}/python/cuvs" + "https://github.com/rapidsai/cuvs/" + "blob/{revision}/python/cuvs/cuvs/" "{package}/{path}#L{lineno}", ) diff --git a/docs/source/cpp_api.rst b/docs/source/cpp_api.rst index 1916abde1a..67bc7f65b2 100644 --- a/docs/source/cpp_api.rst +++ b/docs/source/cpp_api.rst @@ -1,6 +1,6 @@ -~~~~~~~ -C++ API -~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~ +C++ API Documentation +~~~~~~~~~~~~~~~~~~~~~ .. _api: diff --git a/docs/source/cpp_api/core_interop.rst b/docs/source/cpp_api/core_interop.rst index b2ef05f275..a8fcb2890b 100644 --- a/docs/source/cpp_api/core_interop.rst +++ b/docs/source/cpp_api/core_interop.rst @@ -5,7 +5,6 @@ Interop :language: c++ :class: highlight - ``#include `` namespace *cuvs::core* diff --git a/docs/source/cpp_api/neighbors.rst b/docs/source/cpp_api/neighbors.rst index 6ba0014442..61898cec86 100644 --- a/docs/source/cpp_api/neighbors.rst +++ b/docs/source/cpp_api/neighbors.rst @@ -1,7 +1,5 @@ -Neighbors -========= - -This page provides C++ class references for the publicly-exposed elements of the neighbors package. +Nearest Neighbors +================= .. role:: py(code) :language: c++ diff --git a/docs/source/cpp_api/neighbors_cagra.rst b/docs/source/cpp_api/neighbors_cagra.rst index 65fdcb2bcd..aed843dad7 100644 --- a/docs/source/cpp_api/neighbors_cagra.rst +++ b/docs/source/cpp_api/neighbors_cagra.rst @@ -1,9 +1,7 @@ CAGRA ===== -CAGRA is a graph-based nearest neighbors implementation with state-of-the art query performance for both small- and large-batch sized search. - -Please note that the CAGRA implementation is currently experimental and the API is subject to change from release to release. We are currently working on promoting CAGRA to a top-level stable API within RAFT. +CAGRA is a graph-based nearest neighbors algorithm that was built from the ground up for GPU acceleration. CAGRA demonstrates state-of-the art index build and query performance for both small- and large-batch sized search. .. role:: py(code) :language: c++ @@ -13,19 +11,43 @@ Please note that the CAGRA implementation is currently experimental and the API namespace *cuvs::neighbors::cagra* -.. doxygengroup:: cagra +Index build parameters +---------------------- + +.. doxygengroup:: cagra_cpp_index_params :project: cuvs :members: :content-only: +Index search parameters +----------------------- -Serializer Methods ------------------- -``#include `` +.. doxygengroup:: cagra_cpp_search_params + :project: cuvs + :members: + :content-only: -namespace *cuvs::neighbors::cagra* +Index +----- -.. doxygengroup:: cagra_serialize +.. doxygengroup:: cagra_cpp_index :project: cuvs :members: :content-only: + +Index build +----------- + +.. doxygengroup:: cagra_cpp_index_build + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: cagra_cpp_index_search + :project: cuvs + :members: + :content-only: + diff --git a/docs/source/index.rst b/docs/source/index.rst index bf97906101..bbb6f93f27 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,8 +23,7 @@ cuVS is a library for vector search and clustering on the GPU. quick_start.md build.md - cpp_api.rst - python_api.rst + api_docs.rst developer_guide.md contributing.md diff --git a/docs/source/python_api.rst b/docs/source/python_api.rst index fba4d96ba5..999c40fd37 100644 --- a/docs/source/python_api.rst +++ b/docs/source/python_api.rst @@ -1,8 +1,10 @@ -~~~~~~~~~~ -Python API -~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~ +Python API Documentation +~~~~~~~~~~~~~~~~~~~~~~~~ .. _api: .. toctree:: :maxdepth: 4 + + python_api/neighbors.rst \ No newline at end of file diff --git a/docs/source/python_api/neighbors.rst b/docs/source/python_api/neighbors.rst new file mode 100644 index 0000000000..14e907002e --- /dev/null +++ b/docs/source/python_api/neighbors.rst @@ -0,0 +1,12 @@ +Nearest Neighbors +================= + +.. role:: py(code) + :language: python + :class: highlight + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + neighbors_cagra.rst \ No newline at end of file diff --git a/docs/source/python_api/neighbors_cagra.rst b/docs/source/python_api/neighbors_cagra.rst new file mode 100644 index 0000000000..6f64f77635 --- /dev/null +++ b/docs/source/python_api/neighbors_cagra.rst @@ -0,0 +1,36 @@ +CAGRA +===== + +CAGRA is a graph-based nearest neighbors algorithm that was built from the ground up for GPU acceleration. CAGRA demonstrates state-of-the art index build and query performance for both small- and large-batch sized search. + +.. role:: py(code) + :language: python + :class: highlight + +Index build parameters +###################### + +.. autoclass:: cuvs.neighbors.cagra.IndexParams + :members: + +Index search parameters +####################### + +.. autoclass:: cuvs.neighbors.cagra.SearchParams + :members: + +Index +##### + +.. autoclass:: cuvs.neighbors.cagra.Index + :members: + +Index build +########### + +.. autofunction:: cuvs.neighbors.cagra.build_index + +Index search +############ + +.. autofunction:: cuvs.neighbors.cagra.search diff --git a/docs/source/rust_api.rst b/docs/source/rust_api.rst new file mode 100644 index 0000000000..254136e3aa --- /dev/null +++ b/docs/source/rust_api.rst @@ -0,0 +1,8 @@ +~~~~~~~~~~~~~~~~~~~~~~ +Rust API Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. _api: + +.. toctree:: + :maxdepth: 4 diff --git a/docs/source/sphinxext/github_link.py b/docs/source/sphinxext/github_link.py index a7a46fdd9d..2c52488caa 100644 --- a/docs/source/sphinxext/github_link.py +++ b/docs/source/sphinxext/github_link.py @@ -96,7 +96,7 @@ def _linkcode_resolve(domain, info, package, url_fmt, revision): # fn is expected to be the absolute path. fn = os.path.relpath(source_file, start=package) print("{}:{}".format( - os.path.abspath(os.path.join("..", "python", "cuml", fn)), + os.path.abspath(os.path.join("..", "python", "cuvs", fn)), lineno)) else: return diff --git a/cpp/examples/CMakeLists.txt b/examples/cpp/CMakeLists.txt similarity index 100% rename from cpp/examples/CMakeLists.txt rename to examples/cpp/CMakeLists.txt diff --git a/cpp/examples/README.md b/examples/cpp/README.md similarity index 93% rename from cpp/examples/README.md rename to examples/cpp/README.md index 125c6dba22..5a98dd3904 100644 --- a/cpp/examples/README.md +++ b/examples/cpp/README.md @@ -6,7 +6,7 @@ First, please refer to our [installation docs](https://docs.rapids.ai/api/cuvs/s Once the minimum requirements are satisfied, this example template application can be built with the provided `build.sh` script. This is a bash script that calls the appropriate CMake commands, so you can look into it to see the typical CMake based build workflow. -This directory (`CUVS_SOURCE/cpp/examples`) can be copied directly in order to build a new application with CUVS. +This directory (`CUVS_SOURCE/examples/cpp`) can be copied directly in order to build a new application with CUVS. cuVS can be integrated into an existing CMake project by copying the contents in the `configure rapids-cmake` and `configure cuvs` sections of the provided `CMakeLists.txt` into your project, along with `cmake/thirdparty/get_cuvs.cmake`. @@ -14,5 +14,4 @@ Make sure to link against the appropriate Cmake targets. Use `cuvs::cuvs` to uti ```cmake target_link_libraries(your_app_target PRIVATE cuvs::cuvs) -``` - +``` \ No newline at end of file diff --git a/cpp/examples/build.sh b/examples/cpp/build.sh similarity index 100% rename from cpp/examples/build.sh rename to examples/cpp/build.sh diff --git a/cpp/examples/cmake/thirdparty/fetch_rapids.cmake b/examples/cpp/cmake/thirdparty/fetch_rapids.cmake similarity index 100% rename from cpp/examples/cmake/thirdparty/fetch_rapids.cmake rename to examples/cpp/cmake/thirdparty/fetch_rapids.cmake diff --git a/cpp/examples/cmake/thirdparty/get_cuvs.cmake b/examples/cpp/cmake/thirdparty/get_cuvs.cmake similarity index 100% rename from cpp/examples/cmake/thirdparty/get_cuvs.cmake rename to examples/cpp/cmake/thirdparty/get_cuvs.cmake diff --git a/cpp/examples/src/cagra_example.cu b/examples/cpp/src/cagra_example.cu similarity index 100% rename from cpp/examples/src/cagra_example.cu rename to examples/cpp/src/cagra_example.cu diff --git a/cpp/examples/src/common.cuh b/examples/cpp/src/common.cuh similarity index 100% rename from cpp/examples/src/common.cuh rename to examples/cpp/src/common.cuh diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx index 04031414e2..d64ac72ee0 100644 --- a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx @@ -77,6 +77,7 @@ cdef class IndexParams: building the knn graph. It is expected to be generally faster than ivf_pq. """ + cdef cuvsCagraIndexParams* params def __init__(self, *, @@ -122,6 +123,11 @@ cdef class IndexParams: cdef class Index: + """ + CAGRA index object. This object stores the trained CAGRA index state + which can be used to perform nearest neighbors searches. + """ + cdef cuvsCagraIndex_t index cdef bool trained @@ -268,6 +274,7 @@ cdef class SearchParams: rand_xor_mask: int, default = 0x128394 Bit mask used for initial random seed node selection. """ + cdef cuvsCagraSearchParams params def __init__(self, *, From 49b95fffb4c228174ef6ac23154352bc8a8f09c8 Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Fri, 8 Mar 2024 23:39:50 +0100 Subject: [PATCH 008/623] Docs 2404 api docs (#46) Authors: - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Ben Frederickson (https://github.com/benfred) URL: https://github.com/rapidsai/cuvs/pull/46 --- docs/source/build.md | 20 +++++++------------- docs/source/index.rst | 1 - 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/source/build.md b/docs/source/build.md index 939f1db346..976482725c 100644 --- a/docs/source/build.md +++ b/docs/source/build.md @@ -1,8 +1,6 @@ **# Installation -The cuVS software development kit provides APIs for C, C++, Python, and Rust languages. These - -Both the C++ and Python APIs require CMake to build from source. +The cuVS software development kit provides APIs for C, C++, Python, and Rust languages. This guide outlines how to install the pre-compiled packages, build it from source, and use it in downstream applications. ## Table of Contents @@ -25,10 +23,6 @@ Both the C++ and Python APIs require CMake to build from source. - [Build Documentation](#build-documentation) - [Use cuVS in your application](#use-cuvs-in-your-application) -[//]: # (- [Using cuVS in downstream projects](#using-raft-c-in-downstream-projects)) - -[//]: # ( - [CMake targets](#cmake-targets)_) - ------ ## Installing Pre-compiled Packages @@ -39,17 +33,17 @@ The easiest way to install the pre-compiled C, C++, and Python packages is throu #### C++ Package ```bash -mamba install -c rapidsai -c conda-forge -c nvidia libcuvs cuda-version=11.8 +mamba install -c rapidsai -c conda-forge -c nvidia libcuvs cuda-version=12.0 ``` #### C Package ```bash -mamba install -c rapidsai -c conda-forge -c nvidia libcuvs_c cuda-version=11.8 +mamba install -c rapidsai -c conda-forge -c nvidia libcuvs_c cuda-version=12.0 ``` #### Python Package ```bash -mamba install -c rapidsai -c conda-forge -c nvidia pycuvs cuda-version=11.8 +mamba install -c rapidsai -c conda-forge -c nvidia pycuvs cuda-version=12.0 ``` ### Python through Pip @@ -66,7 +60,7 @@ And CUDA 12 packages: pip install pycuvs-cu12 --extra-index-url=https://pypi.nvidia.com ``` -Note: these packages statically links the C and C++ libraries so the `libcuvs` and `libcuvs_c` shared libraries won't be readily available to use in your code. +Note: these packages statically link the C and C++ libraries so the `libcuvs` and `libcuvs_c` shared libraries won't be readily available to use in your code. ### Rust through crates.io @@ -124,10 +118,10 @@ Compile the C and C++ Googletests using the `tests` target in `build.sh`. The tests will be written to the build directory, which is `cpp/build/` by default, and they will be named `*_TEST`. -It can take sometime to compile all of the tests. You can build individual tests by providing a semicolon-separated list to the `--limit-tests` option in `build.sh`. Make sure to pass the `-n` flag so the tests are not installed. +It can take some time to compile all of the tests. You can build individual tests by providing a semicolon-separated list to the `--limit-tests` option in `build.sh`. Make sure to pass the `-n` flag so the tests are not installed. ```bash -./build.sh libcuvs tests -n --limit-tests=NEIGHBORS_TEST;CLUSTER_TEST +./build.sh libcuvs tests -n --limit-tests=NEIGHBORS_TEST;CAGRA_C_TEST ``` ### Python library diff --git a/docs/source/index.rst b/docs/source/index.rst index bbb6f93f27..ce8a8e1d25 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -24,7 +24,6 @@ cuVS is a library for vector search and clustering on the GPU. quick_start.md build.md api_docs.rst - developer_guide.md contributing.md From 0e5aac6127aa325a4d01f5160af84fffc9b11401 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Tue, 12 Mar 2024 15:39:14 -0400 Subject: [PATCH 009/623] Remove hard-coding of RAPIDS version where possible (#45) * Read `VERSION` file from CMake * Read `cuvs.__version__` from docs build * Read `VERSION` file from shell scripts * Use variable substitution in Doxyfile * Update `ci/release/update-version.sh` Issue: https://github.com/rapidsai/build-planning/issues/15 Authors: - Kyle Edwards (https://github.com/KyleFromNVIDIA) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Bradley Dice (https://github.com/bdice) - Jake Awe (https://github.com/AyodeAwe) - Ben Frederickson (https://github.com/benfred) URL: https://github.com/rapidsai/cuvs/pull/45 --- build.sh | 3 +++ ci/build_docs.sh | 9 +++++--- ci/release/update-version.sh | 16 +------------- cpp/CMakeLists.txt | 7 ++---- cpp/cmake/thirdparty/get_raft.cmake | 6 ++--- cpp/doxygen/Doxyfile | 2 +- docs/source/conf.py | 8 +++++-- fetch_rapids.cmake | 20 ----------------- python/cuvs/CMakeLists.txt | 9 +++----- rapids_config.cmake | 34 +++++++++++++++++++++++++++++ 10 files changed, 59 insertions(+), 55 deletions(-) delete mode 100644 fetch_rapids.cmake create mode 100644 rapids_config.cmake diff --git a/build.sh b/build.sh index 8571a574c1..36ab3529c1 100755 --- a/build.sh +++ b/build.sh @@ -391,6 +391,9 @@ if (( ${NUMARGS} == 0 )) || hasArg python; then python -m pip install --no-build-isolation --no-deps -vvv ${REPODIR}/python/cuvs fi +export RAPIDS_VERSION="$(sed -E -e 's/^([0-9]{2})\.([0-9]{2})\.([0-9]{2}).*$/\1.\2.\3/' "${REPODIR}/VERSION")" +export RAPIDS_VERSION_MAJOR_MINOR="$(sed -E -e 's/^([0-9]{2})\.([0-9]{2})\.([0-9]{2}).*$/\1.\2/' "${REPODIR}/VERSION")" + if hasArg docs; then set -x cd ${DOXYGEN_BUILD_DIR} diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 983e973854..e756d09af2 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -18,13 +18,16 @@ rapids-print-env rapids-logger "Downloading artifacts from previous jobs" CPP_CHANNEL=$(rapids-download-conda-from-s3 cpp) -#PYTHON_CHANNEL=$(rapids-download-conda-from-s3 python) +PYTHON_CHANNEL=$(rapids-download-conda-from-s3 python) rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ - libcuvs + --channel "${PYTHON_CHANNEL}" \ + libcuvs cuvs -export RAPIDS_VERSION_NUMBER="24.04" +export RAPIDS_VERSION="$(rapids-version)" +export RAPIDS_VERSION_MAJOR_MINOR="$(rapids-version-major-minor)" +export RAPIDS_VERSION_NUMBER="$RAPIDS_VERSION_MAJOR_MINOR" export RAPIDS_DOCS_DIR="$(mktemp -d)" rapids-logger "Build CPP docs" diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index b122e73bdf..7dc4d12738 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -36,22 +36,11 @@ function sed_runner() { sed -i.bak ''"$1"'' $2 && rm -f ${2}.bak } -sed_runner "s/set(RAPIDS_VERSION .*)/set(RAPIDS_VERSION \"${NEXT_SHORT_TAG}\")/g" cpp/CMakeLists.txt -sed_runner "s/set(RAPIDS_VERSION .*)/set(RAPIDS_VERSION \"${NEXT_SHORT_TAG}\")/g" cpp/template/cmake/thirdparty/fetch_rapids.cmake -sed_runner "s/set(CUVS_VERSION .*)/set(CUVS_VERSION \"${NEXT_FULL_TAG}\")/g" cpp/CMakeLists.txt -sed_runner 's/'"cuvs_version .*)"'/'"cuvs_version ${NEXT_FULL_TAG})"'/g' python/cuvs/CMakeLists.txt -sed_runner 's/'"branch-.*\/RAPIDS.cmake"'/'"branch-${NEXT_SHORT_TAG}\/RAPIDS.cmake"'/g' fetch_rapids.cmake +sed_runner "s/set(RAPIDS_VERSION .*)/set(RAPIDS_VERSION \"${NEXT_SHORT_TAG}\")/g" cpp/examples/cmake/thirdparty/fetch_rapids.cmake # Centralized version file update echo "${NEXT_FULL_TAG}" > VERSION -# Wheel testing script -sed_runner "s/branch-.*/branch-${NEXT_SHORT_TAG}/g" ci/test_wheel_raft_dask.sh - -# Docs update -sed_runner 's/version = .*/version = '"'${NEXT_SHORT_TAG}'"'/g' docs/source/conf.py -sed_runner 's/release = .*/release = '"'${NEXT_FULL_TAG}'"'/g' docs/source/conf.py - DEPENDENCIES=( dask-cuda cuvs @@ -83,9 +72,6 @@ sed_runner "/^ucx_py_version:$/ {n;s/.*/ - \"${NEXT_UCX_PY_VERSION}\"/}" conda/ for FILE in .github/workflows/*.yaml; do sed_runner "/shared-workflows/ s/@.*/@branch-${NEXT_SHORT_TAG}/g" "${FILE}" done -sed_runner "s/RAPIDS_VERSION_NUMBER=\".*/RAPIDS_VERSION_NUMBER=\"${NEXT_SHORT_TAG}\"/g" ci/build_docs.sh - -sed_runner "/^PROJECT_NUMBER/ s|\".*\"|\"${NEXT_SHORT_TAG}\"|g" cpp/doxygen/Doxyfile sed_runner "/^set(CUVS_VERSION/ s|\".*\"|\"${NEXT_SHORT_TAG}\"|g" docs/source/build.md sed_runner "s|branch-[0-9][0-9].[0-9][0-9]|branch-${NEXT_SHORT_TAG}|g" docs/source/build.md diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 73dec92a22..f6d6a2223d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -10,11 +10,8 @@ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express # or implied. See the License for the specific language governing permissions and limitations under # the License. -set(RAPIDS_VERSION "24.04") -set(CUVS_VERSION "24.04.00") - cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) -include(../fetch_rapids.cmake) +include(../rapids_config.cmake) include(rapids-cmake) include(rapids-cpm) include(rapids-export) @@ -34,7 +31,7 @@ endif() project( CUVS - VERSION ${CUVS_VERSION} + VERSION "${RAPIDS_VERSION}" LANGUAGES ${lang_list} ) diff --git a/cpp/cmake/thirdparty/get_raft.cmake b/cpp/cmake/thirdparty/get_raft.cmake index ace0165f71..3675136ee8 100644 --- a/cpp/cmake/thirdparty/get_raft.cmake +++ b/cpp/cmake/thirdparty/get_raft.cmake @@ -11,10 +11,10 @@ # or implied. See the License for the specific language governing permissions and limitations under # the License. -# Use RAPIDS_VERSION from cmake/thirdparty/fetch_rapids.cmake -set(RAFT_VERSION "${RAPIDS_VERSION}") +# Use RAPIDS_VERSION_MAJOR_MINOR from rapids_config.cmake +set(RAFT_VERSION "${RAPIDS_VERSION_MAJOR_MINOR}") set(RAFT_FORK "rapidsai") -set(RAFT_PINNED_TAG "branch-${RAPIDS_VERSION}") +set(RAFT_PINNED_TAG "branch-${RAPIDS_VERSION_MAJOR_MINOR}") function(find_and_configure_raft) set(oneValueArgs VERSION FORK PINNED_TAG COMPILE_LIBRARY ENABLE_NVTX ENABLE_MNMG_DEPENDENCIES) diff --git a/cpp/doxygen/Doxyfile b/cpp/doxygen/Doxyfile index faa31c25d0..614cf43d99 100644 --- a/cpp/doxygen/Doxyfile +++ b/cpp/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "cuVS C++ API" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "24.04" +PROJECT_NUMBER = "$(RAPIDS_VERSION_MAJOR_MINOR)" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/docs/source/conf.py b/docs/source/conf.py index 1089f6a5ca..0d667833ad 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -2,6 +2,9 @@ import os import sys +from packaging.version import Version + +import cuvs # If extensions (or modules to document with autodoc) are in another # directory, add these directories to sys.path here. If the directory @@ -66,10 +69,11 @@ # |version| and |release|, also used in various other places throughout the # built documents. # +CUVS_VERSION = Version(cuvs.__version__) # The short X.Y version. -version = '24.04' +version = f"{CUVS_VERSION.major:02}.{CUVS_VERSION.minor:02}" # The full version, including alpha/beta/rc tags. -release = '24.04.00' +release = f"{CUVS_VERSION.major:02}.{CUVS_VERSION.minor:02}.{CUVS_VERSION.micro:02}" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/fetch_rapids.cmake b/fetch_rapids.cmake deleted file mode 100644 index 330270d662..0000000000 --- a/fetch_rapids.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# ============================================================================= -# Copyright (c) 2022-2024, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -# in compliance with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License -# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing permissions and limitations under -# the License. -# ============================================================================= -if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS.cmake) - file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-24.04/RAPIDS.cmake - ${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS.cmake - ) -endif() - -include(${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS.cmake) diff --git a/python/cuvs/CMakeLists.txt b/python/cuvs/CMakeLists.txt index 33a9420e7f..36a62044ae 100644 --- a/python/cuvs/CMakeLists.txt +++ b/python/cuvs/CMakeLists.txt @@ -14,9 +14,7 @@ cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) -include(../../fetch_rapids.cmake) - -set(cuvs_version 24.04.00) +include(../../rapids_config.cmake) # We always need CUDA for cuvs because the cuvs dependency brings in a header-only cuco dependency # that enables CUDA unconditionally. @@ -25,7 +23,7 @@ rapids_cuda_init_architectures(cuvs_py) project( cuvs_py - VERSION ${cuvs_version} + VERSION "${RAPIDS_VERSION}" LANGUAGES # TODO: Building Python extension modules via the python_extension_module requires the C # language to be enabled here. The test project that is built in scikit-build to verify # various linking options for the python library is hardcoded to build with C, so until @@ -45,7 +43,6 @@ message("CUVS_PY: Searching for existing cuVS C/C++ installations before default ################################################################################ # - Process User Options ------------------------------------------------------ -include(../../fetch_rapids.cmake) include(rapids-cmake) include(rapids-cpm) include(rapids-cython-core) @@ -56,7 +53,7 @@ rapids_cpm_init() # If the user requested it we attempt to find CUVS. if(FIND_CUVS_CPP) - find_package(cuvs ${cuvs_version} REQUIRED) + find_package(cuvs "${RAPIDS_VERSION}" REQUIRED) include(../../cpp/cmake/thirdparty/get_dlpack.cmake) else() set(cuvs_FOUND OFF) diff --git a/rapids_config.cmake b/rapids_config.cmake new file mode 100644 index 0000000000..19906dbc62 --- /dev/null +++ b/rapids_config.cmake @@ -0,0 +1,34 @@ +# ============================================================================= +# Copyright (c) 2018-2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= +file(READ "${CMAKE_CURRENT_LIST_DIR}/VERSION" _rapids_version) +if(_rapids_version MATCHES [[^([0-9][0-9])\.([0-9][0-9])\.([0-9][0-9])]]) + set(RAPIDS_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(RAPIDS_VERSION_MINOR "${CMAKE_MATCH_2}") + set(RAPIDS_VERSION_PATCH "${CMAKE_MATCH_3}") + set(RAPIDS_VERSION_MAJOR_MINOR "${RAPIDS_VERSION_MAJOR}.${RAPIDS_VERSION_MINOR}") + set(RAPIDS_VERSION "${RAPIDS_VERSION_MAJOR}.${RAPIDS_VERSION_MINOR}.${RAPIDS_VERSION_PATCH}") +else() + string(REPLACE "\n" "\n " _rapids_version_formatted " ${_rapids_version}") + message( + FATAL_ERROR + "Could not determine RAPIDS version. Contents of VERSION file:\n${_rapids_version_formatted}") +endif() + +if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS-${RAPIDS_VERSION_MAJOR_MINOR}.cmake") + file( + DOWNLOAD + "https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-${RAPIDS_VERSION_MAJOR_MINOR}/RAPIDS.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS-${RAPIDS_VERSION_MAJOR_MINOR}.cmake") +endif() +include("${CMAKE_CURRENT_BINARY_DIR}/CUVS_RAPIDS-${RAPIDS_VERSION_MAJOR_MINOR}.cmake") From fa4ccc3debac8c16ab1226c2c8cada369fb86e4c Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 12 Mar 2024 13:37:28 -0700 Subject: [PATCH 010/623] Add Rust API docs (#49) This adds the rust API docs to the generated sphinx documentation. The rust docs are generated by `cargo doc` and then copied over to the sphinx `_static` html directory, and included in the generated docs site with an iframe. Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/49 --- build.sh | 4 ++ ci/build_docs.sh | 5 +++ dependencies.yaml | 2 + docs/source/rust_api.rst | 13 ++++-- rust/cuvs/src/cagra/index.rs | 18 +++++++- rust/cuvs/src/cagra/index_params.rs | 1 + rust/cuvs/src/cagra/mod.rs | 63 ++++++++++++++++++++++++++++ rust/cuvs/src/cagra/search_params.rs | 1 + rust/cuvs/src/dlpack.rs | 6 +++ rust/cuvs/src/lib.rs | 5 +++ rust/cuvs/src/resources.rs | 4 ++ 11 files changed, 118 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index 36ab3529c1..8c4ece05e4 100755 --- a/build.sh +++ b/build.sh @@ -54,6 +54,7 @@ LIBCUVS_BUILD_DIR=${LIBCUVS_BUILD_DIR:=${REPODIR}/cpp/build} SPHINX_BUILD_DIR=${REPODIR}/docs DOXYGEN_BUILD_DIR=${REPODIR}/cpp/doxygen PYTHON_BUILD_DIR=${REPODIR}/python/cuvs/_skbuild +RUST_BUILD_DIR=${REPODIR}/rust BUILD_DIRS="${LIBCUVS_BUILD_DIR} ${PYTHON_BUILD_DIR}" # Set defaults for vars modified by flags to this script @@ -398,6 +399,9 @@ if hasArg docs; then set -x cd ${DOXYGEN_BUILD_DIR} doxygen Doxyfile + cd ${RUST_BUILD_DIR} + cargo doc -p cuvs --no-deps + rsync -av ${RUST_BUILD_DIR}/target/doc/ ${SPHINX_BUILD_DIR}/source/_static/rust cd ${SPHINX_BUILD_DIR} sphinx-build -b html source _html fi diff --git a/ci/build_docs.sh b/ci/build_docs.sh index e756d09af2..9b371d1f9e 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -12,7 +12,12 @@ rapids-dependency-file-generator \ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" | tee env.yaml rapids-mamba-retry env create --force -f env.yaml -n docs + +# seeing failures on activating the environment here on unbound locals +# apply workaround from https://github.com/conda/conda/issues/8186#issuecomment-532874667 +set +eu conda activate docs +set -eu rapids-print-env diff --git a/dependencies.yaml b/dependencies.yaml index 7fff7811a8..cd6cb0cf04 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -44,6 +44,8 @@ files: - cupy - docs - py_version + - rust + - build rust: output: none includes: diff --git a/docs/source/rust_api.rst b/docs/source/rust_api.rst index 254136e3aa..8297dbef94 100644 --- a/docs/source/rust_api.rst +++ b/docs/source/rust_api.rst @@ -2,7 +2,14 @@ Rust API Documentation ~~~~~~~~~~~~~~~~~~~~~~ -.. _api: +.. raw:: html -.. toctree:: - :maxdepth: 4 + + + + diff --git a/rust/cuvs/src/cagra/index.rs b/rust/cuvs/src/cagra/index.rs index 3c45efafd2..3394889aad 100644 --- a/rust/cuvs/src/cagra/index.rs +++ b/rust/cuvs/src/cagra/index.rs @@ -21,11 +21,18 @@ use crate::dlpack::ManagedTensor; use crate::error::{check_cuvs, Result}; use crate::resources::Resources; +/// CAGRA ANN Index #[derive(Debug)] pub struct Index(ffi::cuvsCagraIndex_t); impl Index { - /// Builds a new index + /// Builds a new Index from the dataset for efficient search. + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `params` - Parameters for building the index + /// * `dataset` - A row-major matrix on either the host or device to index pub fn build>( res: &Resources, params: &IndexParams, @@ -53,6 +60,15 @@ impl Index { } } + /// Perform a Approximate Nearest Neighbors search on the Index + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `params` - Parameters to use in searching the index + /// * `queries` - A matrix in device memory to query for + /// * `neighbors` - Matrix in device memory that receives the indices of the nearest neighbors + /// * `distances` - Matrix in device memory that receives the distances of the nearest neighbors pub fn search( self, res: &Resources, diff --git a/rust/cuvs/src/cagra/index_params.rs b/rust/cuvs/src/cagra/index_params.rs index 7ff68d3f3f..ecc6605313 100644 --- a/rust/cuvs/src/cagra/index_params.rs +++ b/rust/cuvs/src/cagra/index_params.rs @@ -24,6 +24,7 @@ pub type BuildAlgo = ffi::cuvsCagraGraphBuildAlgo; pub struct IndexParams(pub ffi::cuvsCagraIndexParams_t); impl IndexParams { + /// Returns a new IndexParams pub fn new() -> Result { unsafe { let mut params = core::mem::MaybeUninit::::uninit(); diff --git a/rust/cuvs/src/cagra/mod.rs b/rust/cuvs/src/cagra/mod.rs index 55705c27a1..417ed9b0d8 100644 --- a/rust/cuvs/src/cagra/mod.rs +++ b/rust/cuvs/src/cagra/mod.rs @@ -14,6 +14,69 @@ * limitations under the License. */ +//! CAGRA is a graph-based nearest neighbors implementation with state-of-the art +//! query performance for both small- and large-batch sized search. +//! +//! Example: +//! ``` +//! +//! use cuvs::cagra::{Index, IndexParams, SearchParams}; +//! use cuvs::{ManagedTensor, Resources, Result}; +//! +//! use ndarray::s; +//! use ndarray_rand::rand_distr::Uniform; +//! use ndarray_rand::RandomExt; +//! +//! fn cagra_example() -> Result<()> { +//! let res = Resources::new()?; +//! +//! // Create a new random dataset to index +//! let n_datapoints = 65536; +//! let n_features = 512; +//! let dataset = +//! ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); +//! +//! // build the cagra index +//! let build_params = IndexParams::new()?; +//! let index = Index::build(&res, &build_params, &dataset)?; +//! println!( +//! "Indexed {}x{} datapoints into cagra index", +//! n_datapoints, n_features +//! ); +//! +//! // use the first 4 points from the dataset as queries : will test that we get them back +//! // as their own nearest neighbor +//! let n_queries = 4; +//! let queries = dataset.slice(s![0..n_queries, ..]); +//! +//! let k = 10; +//! +//! // CAGRA search API requires queries and outputs to be on device memory +//! // copy query data over, and allocate new device memory for the distances/ neighbors +//! // outputs +//! let queries = ManagedTensor::from(&queries).to_device(&res)?; +//! let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); +//! let neighbors = ManagedTensor::from(&neighbors_host).to_device(&res)?; +//! +//! let mut distances_host = ndarray::Array::::zeros((n_queries, k)); +//! let distances = ManagedTensor::from(&distances_host).to_device(&res)?; +//! +//! let search_params = SearchParams::new()?; +//! +//! index.search(&res, &search_params, &queries, &neighbors, &distances)?; +//! +//! // Copy back to host memory +//! distances.to_host(&res, &mut distances_host)?; +//! neighbors.to_host(&res, &mut neighbors_host)?; +//! +//! // nearest neighbors should be themselves, since queries are from the +//! // dataset +//! println!("Neighbors {:?}", neighbors_host); +//! println!("Distances {:?}", distances_host); +//! Ok(()) +//! } +//! ``` + mod index; mod index_params; mod search_params; diff --git a/rust/cuvs/src/cagra/search_params.rs b/rust/cuvs/src/cagra/search_params.rs index 11ac92bdd2..14956966ea 100644 --- a/rust/cuvs/src/cagra/search_params.rs +++ b/rust/cuvs/src/cagra/search_params.rs @@ -25,6 +25,7 @@ pub type HashMode = ffi::cuvsCagraHashMode; pub struct SearchParams(pub ffi::cuvsCagraSearchParams_t); impl SearchParams { + /// Returns a new SearchParams object pub fn new() -> Result { unsafe { let mut params = core::mem::MaybeUninit::::uninit(); diff --git a/rust/cuvs/src/dlpack.rs b/rust/cuvs/src/dlpack.rs index b86959db12..a1d4e41c6d 100644 --- a/rust/cuvs/src/dlpack.rs +++ b/rust/cuvs/src/dlpack.rs @@ -19,6 +19,8 @@ use std::convert::From; use crate::error::{check_cuda, Result}; use crate::resources::Resources; +/// ManagedTensor is a wrapper around a dlpack DLManagedTensor object. +/// This lets you pass matrices in device or host memory into cuvs. #[derive(Debug)] pub struct ManagedTensor(ffi::DLManagedTensor); @@ -41,6 +43,8 @@ impl ManagedTensor { bytes } + /// Creates a new ManagedTensor on the current GPU device, and copies + /// the data into it. pub fn to_device(&self, _res: &Resources) -> Result { unsafe { let bytes = self.bytes(); @@ -64,6 +68,8 @@ impl ManagedTensor { Ok(ManagedTensor(ret)) } } + + /// Copies data from device memory into host memory pub fn to_host< T: IntoDtype, S: ndarray::RawData + ndarray::RawDataMut, diff --git a/rust/cuvs/src/lib.rs b/rust/cuvs/src/lib.rs index 7a6f847f5d..273f047237 100644 --- a/rust/cuvs/src/lib.rs +++ b/rust/cuvs/src/lib.rs @@ -14,6 +14,11 @@ * limitations under the License. */ +//! cuVS: Rust bindings for Vector Search on the GPU +//! +//! This crate provides Rust bindings for cuVS, allowing you to run +//! approximate nearest neighbors search on the GPU. + pub mod cagra; mod dlpack; mod error; diff --git a/rust/cuvs/src/resources.rs b/rust/cuvs/src/resources.rs index ad7113e6be..a5c503dc53 100644 --- a/rust/cuvs/src/resources.rs +++ b/rust/cuvs/src/resources.rs @@ -17,10 +17,14 @@ use crate::error::{check_cuvs, Result}; use std::io::{stderr, Write}; +/// Resources are objects that are shared between function calls, +/// and includes things like CUDA streams, cuBLAS handles and other +/// resources that are expensive to create. #[derive(Debug)] pub struct Resources(pub ffi::cuvsResources_t); impl Resources { + /// Returns a new Resources object pub fn new() -> Result { let mut res: ffi::cuvsResources_t = 0; unsafe { From bfb7f57cbe964b83da83543e01c6c663a6c9064a Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 12 Mar 2024 14:06:43 -0700 Subject: [PATCH 011/623] add rust to conda environments (#50) Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/50 --- conda/environments/all_cuda-118_arch-aarch64.yaml | 4 ++++ conda/environments/all_cuda-118_arch-x86_64.yaml | 4 ++++ conda/environments/all_cuda-122_arch-aarch64.yaml | 4 ++++ conda/environments/all_cuda-122_arch-x86_64.yaml | 4 ++++ dependencies.yaml | 1 + 5 files changed, 17 insertions(+) diff --git a/conda/environments/all_cuda-118_arch-aarch64.yaml b/conda/environments/all_cuda-118_arch-aarch64.yaml index d711fc5554..e8854eae6a 100644 --- a/conda/environments/all_cuda-118_arch-aarch64.yaml +++ b/conda/environments/all_cuda-118_arch-aarch64.yaml @@ -9,6 +9,7 @@ channels: dependencies: - breathe - c-compiler +- clang - clang-tools=16.0.6 - clang==16.0.6 - cmake>=3.26.4 @@ -27,6 +28,7 @@ dependencies: - graphviz - gtest>=1.13.0 - ipython +- libclang - libcublas-dev=11.11.3.6 - libcublas=11.11.3.6 - libcurand-dev=10.3.0.86 @@ -35,6 +37,7 @@ dependencies: - libcusolver=11.4.1.48 - libcusparse-dev=11.7.5.86 - libcusparse=11.7.5.86 +- make - nccl>=2.9.9 - ninja - numpy>=1.23 @@ -47,6 +50,7 @@ dependencies: - pytest==7.* - recommonmark - rmm==24.4.* +- rust - scikit-build-core>=0.7.0 - scikit-learn - sphinx-copybutton diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index b4c2c6857b..f8269df7b9 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -9,6 +9,7 @@ channels: dependencies: - breathe - c-compiler +- clang - clang-tools=16.0.6 - clang==16.0.6 - cmake>=3.26.4 @@ -27,6 +28,7 @@ dependencies: - graphviz - gtest>=1.13.0 - ipython +- libclang - libcublas-dev=11.11.3.6 - libcublas=11.11.3.6 - libcurand-dev=10.3.0.86 @@ -35,6 +37,7 @@ dependencies: - libcusolver=11.4.1.48 - libcusparse-dev=11.7.5.86 - libcusparse=11.7.5.86 +- make - nccl>=2.9.9 - ninja - numpy>=1.23 @@ -47,6 +50,7 @@ dependencies: - pytest==7.* - recommonmark - rmm==24.4.* +- rust - scikit-build-core>=0.7.0 - scikit-learn - sphinx-copybutton diff --git a/conda/environments/all_cuda-122_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml index 85d96e5c94..e5940cbed8 100644 --- a/conda/environments/all_cuda-122_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -9,6 +9,7 @@ channels: dependencies: - breathe - c-compiler +- clang - clang-tools=16.0.6 - clang==16.0.6 - cmake>=3.26.4 @@ -28,10 +29,12 @@ dependencies: - graphviz - gtest>=1.13.0 - ipython +- libclang - libcublas-dev - libcurand-dev - libcusolver-dev - libcusparse-dev +- make - nccl>=2.9.9 - ninja - numpy>=1.23 @@ -43,6 +46,7 @@ dependencies: - pytest==7.* - recommonmark - rmm==24.4.* +- rust - scikit-build-core>=0.7.0 - scikit-learn - sphinx-copybutton diff --git a/conda/environments/all_cuda-122_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml index 16cc655c63..1008c02700 100644 --- a/conda/environments/all_cuda-122_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -9,6 +9,7 @@ channels: dependencies: - breathe - c-compiler +- clang - clang-tools=16.0.6 - clang==16.0.6 - cmake>=3.26.4 @@ -28,10 +29,12 @@ dependencies: - graphviz - gtest>=1.13.0 - ipython +- libclang - libcublas-dev - libcurand-dev - libcusolver-dev - libcusparse-dev +- make - nccl>=2.9.9 - ninja - numpy>=1.23 @@ -43,6 +46,7 @@ dependencies: - pytest==7.* - recommonmark - rmm==24.4.* +- rust - scikit-build-core>=0.7.0 - scikit-learn - sphinx-copybutton diff --git a/dependencies.yaml b/dependencies.yaml index cd6cb0cf04..fedf4946ee 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -19,6 +19,7 @@ files: - test_python_common - test_py_cuvs - cupy + - rust test_cpp: output: none includes: From 9c0e38005b9942cba3e3a52e8bf25594fb92bb88 Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Wed, 13 Mar 2024 14:32:34 +0100 Subject: [PATCH 012/623] `quick start` and `integrations` guides for the docs (#47) Authors: - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Ben Frederickson (https://github.com/benfred) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/47 --- .gitignore | 1 + README.md | 8 +- docs/source/api_docs.rst | 4 +- docs/source/basics.rst | 90 +++++++++ docs/source/build.md | 178 ----------------- docs/source/build.rst | 233 +++++++++++++++++++++++ docs/source/getting_started.rst | 12 ++ docs/source/index.rst | 5 +- docs/source/integrations.rst | 40 ++++ docs/source/interoperability.rst | 72 +++++++ docs/source/quick_start.md | 183 ------------------ docs/source/working_with_ann_indexes.rst | 102 ++++++++++ 12 files changed, 559 insertions(+), 369 deletions(-) create mode 100644 docs/source/basics.rst delete mode 100644 docs/source/build.md create mode 100644 docs/source/build.rst create mode 100644 docs/source/getting_started.rst create mode 100644 docs/source/integrations.rst create mode 100644 docs/source/interoperability.rst delete mode 100644 docs/source/quick_start.md create mode 100644 docs/source/working_with_ann_indexes.rst diff --git a/.gitignore b/.gitignore index 8cac2b5aed..9fcde3fb3b 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ _xml # sphinx _html _text +docs/source/_static/rust # clang tooling compile_commands.json diff --git a/README.md b/README.md index 14bb028123..4a2d2de166 100755 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ ## Useful Resources -- [cuVS Reference Documentation](https://docs.rapids.ai/api/cuvs/stable/): API Documentation. -- [cuVS Getting Started](./docs/source/quick_start.md): Getting started with RAFT. -- [Build and Install cuVS](./docs/source/build.md): Instructions for installing and building cuVS. -- [Example Notebooks](./notebooks): Example jupyer notebooks +- [Code Examples](https://github.com/rapidsai/cuvs/tree/HEAD/examples): Self-contained Code Examples. +- [API Reference Documentation](https://docs.rapids.ai/api/cuvs/nightly/api_docs): API Documentation. +- [Getting Started Guide](https://docs.rapids.ai/api/cuvs/nightly/getting_started): Getting started with RAFT. +- [Build and Install Guide](https://docs.rapids.ai/api/cuvs/nightly/build): Instructions for installing and building cuVS. - [RAPIDS Community](https://rapids.ai/community.html): Get help, contribute, and collaborate. - [GitHub repository](https://github.com/rapidsai/cuvs): Download the cuVS source code. - [Issue tracker](https://github.com/rapidsai/cuvs/issues): Report issues or request features. diff --git a/docs/source/api_docs.rst b/docs/source/api_docs.rst index 2a5a867e0a..c911539b95 100644 --- a/docs/source/api_docs.rst +++ b/docs/source/api_docs.rst @@ -1,5 +1,5 @@ -API Documentation -================= +API Reference +============= .. toctree:: :maxdepth: 1 diff --git a/docs/source/basics.rst b/docs/source/basics.rst new file mode 100644 index 0000000000..4842e27889 --- /dev/null +++ b/docs/source/basics.rst @@ -0,0 +1,90 @@ +cuVS API Basics +=============== + +- `Memory management`_ +- `Resource management`_ + +Memory management +----------------- + +Centralized memory management allows flexible configuration of allocation strategies, such as sharing the same CUDA memory pool across library boundaries. cuVS uses the [RMM](https://github.com/rapidsai/rmm) library, which eases the burden of configuring different allocation strategies globally across GPU-accelerated libraries. + +RMM currently has APIs for C++ and Python. + +C++ +^^^ + +Here's an example of configuring RMM to use a pool allocator in C++ (derived from the RMM example `here `_): + +.. code-block:: c++ + + rmm::mr::cuda_memory_resource cuda_mr; + // Construct a resource that uses a coalescing best-fit pool allocator + // With the pool initially half of available device memory + auto initial_size = rmm::percent_of_free_device_memory(50); + rmm::mr::pool_memory_resource pool_mr{&cuda_mr, initial_size}; + rmm::mr::set_current_device_resource(&pool_mr); // Updates the current device resource pointer to `pool_mr` + rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource(); // Points to `pool_mr` + +Python +^^^^^^ + +And the corresponding code in Python (derived from the RMM example `here `_): + +.. code-block:: python + + import rmm + pool = rmm.mr.PoolMemoryResource( + rmm.mr.CudaMemoryResource(), + initial_pool_size=2**30, + maximum_pool_size=2**32) + rmm.mr.set_current_device_resource(pool) + + +Resource management +------------------- + +cuVS uses an API from the `RAFT `_ library of ML and data mining primitives to centralize and reuse expensive resources, such as memory management. The below code examples demonstrate how to create these resources for use throughout this guide. + +See RAFT's `resource API documentation `_ for more information. + +C +^ + +.. code-block:: c + + #include + #include + + cuvsResources_t res; + cuvsResourcesCreate(&res); + + // ... do some processing ... + + cuvsResourcesDestroy(res); + +C++ +^^^ + +.. code-block:: c++ + + #include + + raft::device_resources res; + +Python +^^^^^^ + +.. code-block:: python + + import pylibraft + + res = pylibraft.common.DeviceResources() + + +Rust +^^^^ + +.. code-block:: rust + + let res = cuvs::Resources::new()?; \ No newline at end of file diff --git a/docs/source/build.md b/docs/source/build.md deleted file mode 100644 index 976482725c..0000000000 --- a/docs/source/build.md +++ /dev/null @@ -1,178 +0,0 @@ -**# Installation - -The cuVS software development kit provides APIs for C, C++, Python, and Rust languages. This guide outlines how to install the pre-compiled packages, build it from source, and use it in downstream applications. - -## Table of Contents - -- [Install pre-compiled packages](#installing-precompiled-packages) - - [C, C++, and Python through Conda](#installing-through-conda) - - [Python through Pip](#installing-python-through-pip) - - [Rust through crates.io](#installing-rust-through-crates) -- [Build from source](#building-c-and-python-from-source) - - [Prerequisites](#build-prerequisites) - - [CUDA/GPU requirements](#cudagpu-requirements) - - [Build dependencies](#build-dependencies) - - [Required](#required) - - [Create a build environment](#creating-a-build-environment) - - [ccache and sccache](#ccache-and-sccache) - - [C/C++ Libraries](#c-library) - - [Building the Googletests](#building-the-googletests) - - [Python Library](#python-library) - - [Rust Library](#rust-library) - - [Using Cmake Directly](#using-cmake-directly) -- [Build Documentation](#build-documentation) -- [Use cuVS in your application](#use-cuvs-in-your-application) - ------- - -## Installing Pre-compiled Packages - -### C, C++, and Python through Conda - -The easiest way to install the pre-compiled C, C++, and Python packages is through [conda](https://docs.anaconda.com/free/miniconda/index.html). Use the following commands, depending on your CUDA version, to install cuVS packages (replace `rapidsai` with `rapidsai-nightly` to install more up-to-date but less stable nightly packages). `mamba` is preferred over the `conda` command. - -#### C++ Package -```bash -mamba install -c rapidsai -c conda-forge -c nvidia libcuvs cuda-version=12.0 -``` - -#### C Package -```bash -mamba install -c rapidsai -c conda-forge -c nvidia libcuvs_c cuda-version=12.0 -``` - -#### Python Package -```bash -mamba install -c rapidsai -c conda-forge -c nvidia pycuvs cuda-version=12.0 -``` - -### Python through Pip - -The cuVS Python package can also be [installed through pip](https://rapids.ai/pip.html#install). - -For CUDA 11 packages: -```bash -pip install pycuvs-cu11 --extra-index-url=https://pypi.nvidia.com -``` - -And CUDA 12 packages: -```bash -pip install pycuvs-cu12 --extra-index-url=https://pypi.nvidia.com -``` - -Note: these packages statically link the C and C++ libraries so the `libcuvs` and `libcuvs_c` shared libraries won't be readily available to use in your code. - -### Rust through crates.io - -## Build from source - -The core cuVS source code is written in C++ and wrapped through a C API. The C API is wrapped around the C++ APIs and the other supported languages are built around the C API. - - -### Prerequisites - -- Cmake 3.26.4+ -- GCC 9.3+ (11.4+ recommended) -- CUDA Toolkit 11.4+ -- Volta architecture or better (compute capability >= 7.0) - -### Create a build environment - -[Conda](https://docs.anaconda.com/free/miniconda/index.html) environment scripts are provided for installing the necessary dependencies to build cuVS from source. It is preferred to use `mamba`, as it provides significant speedup over `conda`: -```bash -mamba env create --name cuvs -f conda/environments/all_cuda-120_arch-x86_64.yaml -mamba activate cuvs -``` - -The process for building from source with CUDA 11 differs slightly in that your host system will also need to have CUDA toolkit installed which is greater than, or equal to, the version you install into you conda environment. Installing CUDA toolkit into your host system is necessary because `nvcc` is not provided with Conda's cudatoolkit dependencies for CUDA 11. The following example will install create and install dependencies for a CUDA 11.8 conda environment -```bash -mamba env create --name cuvs -f conda/environments/all_cuda-118_arch-x86_64.yaml -mamba activate cuvs -``` - -The recommended way to build and install cuVS from source is to use the `build.sh` script in the root of the repository. This script can build both the C++ and Python artifacts and provides CMake options for building and installing the headers, tests, benchmarks, and the pre-compiled shared library. - - -### C and C++ libraries - -The C and C++ shared libraries are built together using the following arguments to `build.sh`: -```bash -./build.sh libcuvs -``` - -In above example the `libcuvs.so` and `libcuvs_c.so` shared libraries are installed by default into `$INSTALL_PREFIX/lib`. To disable this, pass `-n` flag. - -Once installed, the shared libraries, headers (and any dependencies downloaded and installed via `rapids-cmake`) can be uninstalled using `build.sh`: -```bash -./build.sh libcuvs --uninstall -``` - - -#### Building the Googletests - -Compile the C and C++ Googletests using the `tests` target in `build.sh`. - -```bash -./build.sh libcuvs tests -``` - -The tests will be written to the build directory, which is `cpp/build/` by default, and they will be named `*_TEST`. - -It can take some time to compile all of the tests. You can build individual tests by providing a semicolon-separated list to the `--limit-tests` option in `build.sh`. Make sure to pass the `-n` flag so the tests are not installed. - -```bash -./build.sh libcuvs tests -n --limit-tests=NEIGHBORS_TEST;CAGRA_C_TEST -``` - -### Python library - -The Python library should be built and installed using the `build.sh` script: - -```bash -./build.sh python -``` - -The Python packages can also be uninstalled using the `build.sh` script: -```bash -./build.sh python --uninstall -``` - -### Rust library - - - -### Using CMake directly - -When building cuVS from source, the `build.sh` script offers a nice wrapper around the `cmake` commands to ease the burdens of manually configuring the various available cmake options. When more fine-grained control over the CMake configuration is desired, the `cmake` command can be invoked directly as the below example demonstrates. - -The `CMAKE_INSTALL_PREFIX` installs cuVS into a specific location. The example below installs cuVS into the current Conda environment: -```bash -cd cpp -mkdir build -cd build -cmake -D BUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX ../ -make -j install -``` - -cuVS has the following configurable cmake flags available: - -| Flag | Possible Values | Default Value | Behavior | -|------------------------|----------------------| --- |------------------------------------------------------------------------------| -| BUILD_TESTS | ON, OFF | ON | Compile Googletests | -| CUDA_ENABLE_KERNELINFO | ON, OFF | OFF | Enables `kernelinfo` in nvcc. This is useful for `compute-sanitizer` | -| CUDA_ENABLE_LINEINFO | ON, OFF | OFF | Enable the -lineinfo option for nvcc | -| CUDA_STATIC_RUNTIME | ON, OFF | OFF | Statically link the CUDA runtime | -| DETECT_CONDA_ENV | ON, OFF | ON | Enable detection of conda environment for dependencies | -| CUVS_NVTX | ON, OFF | OFF | Enable NVTX Markers | - -### Build documentation - -The documentation requires that the C, C++ and Python libraries have been built and installed. The following will build the docs along with the necessary libraries: - -``` -./build.sh libcuvs python docs -``` - -## Use cuVS in your application - -The [examples/](https://github.com/rapidsai/raft/tree/HEAD/examples) directory at the root of the cuVS repository has self-contains drop-in projects to build and use the cuVS SDK in your applications. \ No newline at end of file diff --git a/docs/source/build.rst b/docs/source/build.rst new file mode 100644 index 0000000000..8140224656 --- /dev/null +++ b/docs/source/build.rst @@ -0,0 +1,233 @@ +Installation +============ + +The cuVS software development kit provides APIs for C, C++, Python, and Rust languages. This guide outlines how to install the pre-compiled packages, build it from source, and use it in downstream applications. + +- `Installing pre-compiled packages`_ + + * `C, C++, and Python through Conda`_ + + * `Python through Pip`_ + + * `Rust through crates.io`_ + +- `Build from source`_ + + * `Prerequisites`_ + + * `Create a build environment`_ + + * `C and C++ Libraries`_ + + * `Building the Googletests`_ + + * `Python Library`_ + + * `Rust Library`_ + + * `Using Cmake Directly`_ + +- `Build Documentation`_ + + +Installing Pre-compiled Packages +-------------------------------- + +C, C++, and Python through Conda +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The easiest way to install the pre-compiled C, C++, and Python packages is through `conda `_. Use the following commands, depending on your CUDA version, to install cuVS packages (replace `rapidsai` with `rapidsai-nightly` to install more up-to-date but less stable nightly packages). `mamba` is preferred over the `conda` command. + +C++ Package +~~~~~~~~~~~ + +.. code-block:: bash + + mamba install -c rapidsai -c conda-forge -c nvidia libcuvs cuda-version=12.0 + +C Package +~~~~~~~~~ + +.. code-block:: bash + + mamba install -c rapidsai -c conda-forge -c nvidia libcuvs_c cuda-version=12.0 + +Python Package +~~~~~~~~~~~~~~ + +.. code-block:: bash + + mamba install -c rapidsai -c conda-forge -c nvidia cuvs cuda-version=12.0 + +Python through Pip +^^^^^^^^^^^^^^^^^^ + +The cuVS Python package can also be `installed through pip `_. + +For CUDA 11 packages: + +.. code-block:: bash + + pip install cuvs-cu11 --extra-index-url=https://pypi.nvidia.com + +And CUDA 12 packages: + +.. code-block:: bash + + pip install cuvs-cu12 --extra-index-url=https://pypi.nvidia.com + +Note: these packages statically link the C and C++ libraries so the `libcuvs` and `libcuvs_c` shared libraries won't be readily available to use in your code. + +Rust through crates.io +^^^^^^^^^^^^^^^^^^^^^^ + +Build from source +----------------- + +The core cuVS source code is written in C++ and wrapped through a C API. The C API is wrapped around the C++ APIs and the other supported languages are built around the C API. + + +Prerequisites +^^^^^^^^^^^^^ + +- Cmake 3.26.4+ +- GCC 9.3+ (11.4+ recommended) +- CUDA Toolkit 11.4+ +- Volta architecture or better (compute capability >= 7.0) + +Create a build environment +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`Conda `_ environment scripts are provided for installing the necessary dependencies to build cuVS from source. It is preferred to use `mamba`, as it provides significant speedup over `conda`: + +.. code-block:: bash + + mamba env create --name cuvs -f conda/environments/all_cuda-120_arch-x86_64.yaml + mamba activate cuvs + +The process for building from source with CUDA 11 differs slightly in that your host system will also need to have CUDA toolkit installed which is greater than, or equal to, the version you install into you conda environment. Installing CUDA toolkit into your host system is necessary because `nvcc` is not provided with Conda's cudatoolkit dependencies for CUDA 11. The following example will install create and install dependencies for a CUDA 11.8 conda environment + +.. code-block:: bash + + mamba env create --name cuvs -f conda/environments/all_cuda-118_arch-x86_64.yaml + mamba activate cuvs + +The recommended way to build and install cuVS from source is to use the `build.sh` script in the root of the repository. This script can build both the C++ and Python artifacts and provides CMake options for building and installing the headers, tests, benchmarks, and the pre-compiled shared library. + + +C and C++ libraries +^^^^^^^^^^^^^^^^^^^ + +The C and C++ shared libraries are built together using the following arguments to `build.sh`: + +.. code-block:: bash + + ./build.sh libcuvs + +In above example the `libcuvs.so` and `libcuvs_c.so` shared libraries are installed by default into `$INSTALL_PREFIX/lib`. To disable this, pass `-n` flag. + +Once installed, the shared libraries, headers (and any dependencies downloaded and installed via `rapids-cmake`) can be uninstalled using `build.sh`: + +.. code-block:: bash + + ./build.sh libcuvs --uninstall + + +Building the Googletests +~~~~~~~~~~~~~~~~~~~~~~~~ + +Compile the C and C++ Googletests using the `tests` target in `build.sh`. + +.. code-block:: bash + + ./build.sh libcuvs tests + +The tests will be written to the build directory, which is `cpp/build/` by default, and they will be named `*_TEST`. + +It can take some time to compile all of the tests. You can build individual tests by providing a semicolon-separated list to the `--limit-tests` option in `build.sh`. Make sure to pass the `-n` flag so the tests are not installed. + +.. code-block:: bash + + ./build.sh libcuvs tests -n --limit-tests=NEIGHBORS_TEST;CAGRA_C_TEST + +Python library +^^^^^^^^^^^^^^ + +The Python library should be built and installed using the `build.sh` script: + +.. code-block:: bash + + ./build.sh python + +The Python packages can also be uninstalled using the `build.sh` script: + +.. code-block:: bash + + ./build.sh python --uninstall + +Rust library +^^^^^^^^^^^^ + +Using CMake directly +^^^^^^^^^^^^^^^^^^^^ + +When building cuVS from source, the `build.sh` script offers a nice wrapper around the `cmake` commands to ease the burdens of manually configuring the various available cmake options. When more fine-grained control over the CMake configuration is desired, the `cmake` command can be invoked directly as the below example demonstrates. + +The `CMAKE_INSTALL_PREFIX` installs cuVS into a specific location. The example below installs cuVS into the current Conda environment: + +.. code-block:: bash + + cd cpp + mkdir build + cd build + cmake -D BUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX ../ + make -j install + +cuVS has the following configurable cmake flags available: + +.. list-table:: Cmake Flags + + * - Flag + - Possible Values + - Default Value + - Behavior + + * - BUILD_TESTS + - ON, OFF + - ON + - Compile Googletests + + * - CUDA_ENABLE_KERNELINFO + - ON, OFF + - OFF + - Enables `kernelinfo` in nvcc. This is useful for `compute-sanitizer` + + * - CUDA_ENABLE_LINEINFO + - ON, OFF + - OFF + - Enable the `-lineinfo` option for nvcc + + * - CUDA_STATIC_RUNTIME + - ON, OFF + - OFF + - Statically link the CUDA runtime + + * - DETECT_CONDA_ENV + - ON, OFF + - ON + - Enable detection of conda environment for dependencies + + * - CUVS_NVTX + - ON, OFF + - OFF + - Enable NVTX markers + + +Build documentation +^^^^^^^^^^^^^^^^^^^ + +The documentation requires that the C, C++ and Python libraries have been built and installed. The following will build the docs along with the necessary libraries: + +.. code-block:: bash + + ./build.sh libcuvs python docs diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst new file mode 100644 index 0000000000..79b35c2d50 --- /dev/null +++ b/docs/source/getting_started.rst @@ -0,0 +1,12 @@ +Getting Started +=============== + +This guide provides an initial starting point of the basic concepts and using the various APIs in the cuVS software development kit. + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + basics.rst + interoperability.rst + working_with_ann_indexes.rst \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index ce8a8e1d25..b8334e8589 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,7 @@ Useful Resources .. _cuvs_reference: https://docs.rapids.ai/api/cuvs/stable/ -- `Example Notebooks `_: Example Jupyter notebooks +- `Example Notebooks `_: Self-contained Code examples - `RAPIDS Community `_: Get help, contribute, and collaborate. - `GitHub repository `_: Download the cuVS source code. - `Issue tracker `_: Report issues or request features. @@ -21,8 +21,9 @@ cuVS is a library for vector search and clustering on the GPU. :maxdepth: 1 :caption: Contents: - quick_start.md + getting_started.rst build.md + integrations.rst api_docs.rst contributing.md diff --git a/docs/source/integrations.rst b/docs/source/integrations.rst new file mode 100644 index 0000000000..6c6e2ce8ca --- /dev/null +++ b/docs/source/integrations.rst @@ -0,0 +1,40 @@ +Integrations +============ + +Aside from using cuVS directly, it can be consumed through a number of sdk and vector database integrations. + +- `FAISS`_ +- `Milvus`_ +- `Kinetica`_ + +FAISS +----- + +FAISS v1.8 provides a special conda package that enables a RAFT backend for the Flat, IVF-Flat and IVF-PQ indexes on the GPU. Like the classical FAISS GPU indexes, the RAFT backend also enables interoperability between FAISS CPU indexes, allowing an index to be trained on GPU, searched on CPU, and vice versa. + +The RAFT backend can be enabled by building FAISS from source with the `FAISS_USE_RAFT` cmake flag enabled and setting the `use_raft` configuration option for the RAFT-enabled GPU indexes. + +A pre-compiled conda package can also be installed using the following command: + +.. code-block:: bash + + conda install -c conda-forge -c pytorch -c rapidsai -c nvidia -c "nvidia/label/cuda-11.8.0" faiss-gpu-raft + +The next release of FAISS will feature cuVS support as we continue to migrate the vector search algorithms from RAFT to cuVS. + +Milvus +------ + +In version 2.3, Milvus released support for IVF-Flat and IVF-PQ indexes on the GPU through RAFT. Version 2.4 adds support for brute-force and the graph-based CAGRA index on the GPU. Please refer to the `Milvus documentation `_ to install Milvus with GPU support. + +The GPU indexes can be enabled by using the index types prefixed with `GPU_`, as outlined in the `Milvus index build guide `_. + +Milvus will be migrating their GPU support from RAFT to cuVS as we continue to move the vector search algorithms out of RAFT and into cuVS. + + +Kinetica +-------- + +Starting with release 7.2, Kinetica supports the graph-based the CAGRA algorithm from RAFT. Kinetica will continue to improve its support over coming versions, while also migrating to cuVS as we work to move the vector search algorithms out of RAFT and into cuVS. + +Kinetica currently offers the ability to create a CAGRA index in a SQL `CREATE_TABLE` statement, as outlined in their `vector search indexing docs `_. Kinetica is not open source, but the RAFT indexes can be enabled in the developer edition, which can be installed `here `_. \ No newline at end of file diff --git a/docs/source/interoperability.rst b/docs/source/interoperability.rst new file mode 100644 index 0000000000..fc8d19ef43 --- /dev/null +++ b/docs/source/interoperability.rst @@ -0,0 +1,72 @@ +Interoperability +================ + +DLPack (C) +^^^^^^^^^^ + +Multi-dimensional span (C++) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +cuVS is built on top of the GPU-accelerated machine learning and data mining primitives in the `RAFT `_ library. Most of the C++ APIs in cuVS accept `mdspan `_ multi-dimensional array view for representing data in higher dimensions similar to the `ndarray` in the Numpy Python library. RAFT also contains the corresponding owning `mdarray` structure, which simplifies the allocation and management of multi-dimensional data in both host and device (GPU) memory. + +The `mdarray` is an owning object that forms a convenience layer over RMM and can be constructed in RAFT using a number of different helper functions: + +.. code-block:: c++ + + #include + + int n_rows = 10; + int n_cols = 10; + + auto scalar = raft::make_device_scalar(handle, 1.0); + auto vector = raft::make_device_vector(handle, n_cols); + auto matrix = raft::make_device_matrix(handle, n_rows, n_cols); + +The `mdspan` is a lightweight non-owning view that can wrap around any pointer, maintaining shape, layout, and indexing information for accessing elements. + +We can construct `mdspan` instances directly from the above `mdarray` instances: + +.. code-block:: c++ + + // Scalar mdspan on device + auto scalar_view = scalar.view(); + + // Vector mdspan on device + auto vector_view = vector.view(); + + // Matrix mdspan on device + auto matrix_view = matrix.view(); + +Since the `mdspan` is just a lightweight wrapper, we can also construct it from the underlying data handles in the `mdarray` instances above. We use the extent to get information about the `mdarray` or `mdspan`'s shape. + +.. code-block:: c++ + + #include + + auto scalar_view = raft::make_device_scalar_view(scalar.data_handle()); + auto vector_view = raft::make_device_vector_view(vector.data_handle(), vector.extent(0)); + auto matrix_view = raft::make_device_matrix_view(matrix.data_handle(), matrix.extent(0), matrix.extent(1)); + +Of course, RAFT's `mdspan`/`mdarray` APIs aren't just limited to the `device`. You can also create `host` variants: + +.. code-block:: c++ + + #include + #include + + int n_rows = 10; + int n_cols = 10; + + auto scalar = raft::make_host_scalar(handle, 1.0); + auto vector = raft::make_host_vector(handle, n_cols); + auto matrix = raft::make_host_matrix(handle, n_rows, n_cols); + + auto scalar_view = raft::make_host_scalar_view(scalar.data_handle()); + auto vector_view = raft::make_host_vector_view(vector.data_handle(), vector.extent(0)); + auto matrix_view = raft::make_host_matrix_view(matrix.data_handle(), matrix.extent(0), matrix.extent(1)); + +Please refer to RAFT's `mdspan` `documentation `_ to learn more. + + +CUDA array interface (Python) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/quick_start.md b/docs/source/quick_start.md deleted file mode 100644 index 3909b40f20..0000000000 --- a/docs/source/quick_start.md +++ /dev/null @@ -1,183 +0,0 @@ -# Quick Start - -This guide is meant to provide a quick-start tutorial for interacting with RAFT's C++ & Python APIs. - -## RAPIDS Memory Manager (RMM) - -RAFT relies heavily on the [RMM](https://github.com/rapidsai/rmm) library which eases the burden of configuring different allocation strategies globally across the libraries that use it. - -## Multi-dimensional Spans and Arrays - -Most of the APIs in RAFT accept [mdspan](https://arxiv.org/abs/2010.06474) multi-dimensional array view for representing data in higher dimensions similar to the `ndarray` in the Numpy Python library. RAFT also contains the corresponding owning `mdarray` structure, which simplifies the allocation and management of multi-dimensional data in both host and device (GPU) memory. - -The `mdarray` is an owning object that forms a convenience layer over RMM and can be constructed in RAFT using a number of different helper functions: - -```c++ -#include - -int n_rows = 10; -int n_cols = 10; - -auto scalar = raft::make_device_scalar(handle, 1.0); -auto vector = raft::make_device_vector(handle, n_cols); -auto matrix = raft::make_device_matrix(handle, n_rows, n_cols); -``` - -The `mdspan` is a lightweight non-owning view that can wrap around any pointer, maintaining shape, layout, and indexing information for accessing elements. - - -We can construct `mdspan` instances directly from the above `mdarray` instances: - -```c++ -// Scalar mdspan on device -auto scalar_view = scalar.view(); - -// Vector mdspan on device -auto vector_view = vector.view(); - -// Matrix mdspan on device -auto matrix_view = matrix.view(); -``` -Since the `mdspan` is just a lightweight wrapper, we can also construct it from the underlying data handles in the `mdarray` instances above. We use the extent to get information about the `mdarray` or `mdspan`'s shape. - -```c++ -#include - -auto scalar_view = raft::make_device_scalar_view(scalar.data_handle()); -auto vector_view = raft::make_device_vector_view(vector.data_handle(), vector.extent(0)); -auto matrix_view = raft::make_device_matrix_view(matrix.data_handle(), matrix.extent(0), matrix.extent(1)); -``` - -Of course, RAFT's `mdspan`/`mdarray` APIs aren't just limited to the `device`. You can also create `host` variants: - -```c++ -#include -#include - -int n_rows = 10; -int n_cols = 10; - -auto scalar = raft::make_host_scalar(handle, 1.0); -auto vector = raft::make_host_vector(handle, n_cols); -auto matrix = raft::make_host_matrix(handle, n_rows, n_cols); - -auto scalar_view = raft::make_host_scalar_view(scalar.data_handle()); -auto vector_view = raft::make_host_vector_view(vector.data_handle(), vector.extent(0)); -auto matrix_view = raft::make_host_matrix_view(matrix.data_handle(), matrix.extent(0), matrix.extent(1)); -``` - -And `managed` variants: - -```c++ -#include - -int n_rows = 10; -int n_cols = 10; - -auto matrix = raft::make_managed_mdspan(managed_ptr, raft::make_matrix_extents(n_rows, n_cols)); -``` - -You can also create strided mdspans: - -```c++ - -#include - -int n_elements = 10; -int stride = 10; - -auto vector = raft::make_device_vector_view(vector_ptr, raft::make_vector_strided_layout(n_elements, stride)); -``` - - -## C++ Example - -Most of the primitives in RAFT accept a `raft::handle_t` object for the management of resources which are expensive to create, such CUDA streams, stream pools, and handles to other CUDA libraries like `cublas` and `cusolver`. - -The example below demonstrates creating a RAFT handle and using it with `device_matrix` and `device_vector` to allocate memory, generating random clusters, and computing -pairwise Euclidean distances: - -```c++ -#include -#include -#include -#include - -raft::handle_t handle; - -int n_samples = 5000; -int n_features = 50; - -auto input = raft::make_device_matrix(handle, n_samples, n_features); -auto labels = raft::make_device_vector(handle, n_samples); -auto output = raft::make_device_matrix(handle, n_samples, n_samples); - -raft::random::make_blobs(handle, input.view(), labels.view()); - -auto metric = raft::distance::DistanceType::L2SqrtExpanded; -raft::distance::pairwise_distance(handle, input.view(), input.view(), output.view(), metric); -``` - -## Python Example - -The `pylibraft` package contains a Python API for RAFT algorithms and primitives. `pylibraft` integrates nicely into other libraries by being very lightweight with minimal dependencies and accepting any object that supports the `__cuda_array_interface__`, such as [CuPy's ndarray](https://docs.cupy.dev/en/stable/user_guide/interoperability.html#rmm). The number of RAFT algorithms exposed in this package is continuing to grow from release to release. - -The example below demonstrates computing the pairwise Euclidean distances between CuPy arrays. Note that CuPy is not a required dependency for `pylibraft`. - -```python -import cupy as cp - -from pylibraft.distance import pairwise_distance - -n_samples = 5000 -n_features = 50 - -in1 = cp.random.random_sample((n_samples, n_features), dtype=cp.float32) -in2 = cp.random.random_sample((n_samples, n_features), dtype=cp.float32) - -output = pairwise_distance(in1, in2, metric="euclidean") -``` - -The `output` array in the above example is of type `raft.common.device_ndarray`, which supports [__cuda_array_interface__](https://numba.pydata.org/numba-doc/dev/cuda/cuda_array_interface.html#cuda-array-interface-version-2) making it interoperable with other libraries like CuPy, Numba, and PyTorch that also support it. CuPy supports DLPack, which also enables zero-copy conversion from `raft.common.device_ndarray` to JAX and Tensorflow. - -Below is an example of converting the output `pylibraft.common.device_ndarray` to a CuPy array: -```python -cupy_array = cp.asarray(output) -``` - -And converting to a PyTorch tensor: -```python -import torch - -torch_tensor = torch.as_tensor(output, device='cuda') -``` - -When the corresponding library has been installed and available in your environment, this conversion can also be done automatically by all RAFT compute APIs by setting a global configuration option: -```python -import pylibraft.config -pylibraft.config.set_output_as("cupy") # All compute APIs will return cupy arrays -pylibraft.config.set_output_as("torch") # All compute APIs will return torch tensors -``` - -You can also specify a `callable` that accepts a `pylibraft.common.device_ndarray` and performs a custom conversion. The following example converts all output to `numpy` arrays: -```python -pylibraft.config.set_output_as(lambda device_ndarray: return device_ndarray.copy_to_host()) -``` - - -`pylibraft` also supports writing to a pre-allocated output array so any `__cuda_array_interface__` supported array can be written to in-place: - -```python -import cupy as cp - -from pylibraft.distance import pairwise_distance - -n_samples = 5000 -n_features = 50 - -in1 = cp.random.random_sample((n_samples, n_features), dtype=cp.float32) -in2 = cp.random.random_sample((n_samples, n_features), dtype=cp.float32) -output = cp.empty((n_samples, n_samples), dtype=cp.float32) - -pairwise_distance(in1, in2, out=output, metric="euclidean") -``` diff --git a/docs/source/working_with_ann_indexes.rst b/docs/source/working_with_ann_indexes.rst new file mode 100644 index 0000000000..f347a2f428 --- /dev/null +++ b/docs/source/working_with_ann_indexes.rst @@ -0,0 +1,102 @@ +Working with ANN Indexes +======================== + +- `Building an index`_ +- `Searching an index`_ +- `CPU/GPU Interoperability`_ +- `Serializing an index`_ + +Building an index +----------------- + +C +^ + +.. code-block:: c + + #include + + cuvsResources_t res; + cuvsCagraIndexParams_t index_params; + cuvsCagraIndex_t index; + + DLManagedTensor *dataset; + load_dataset(dataset); + + cuvsResourcesCreate(&res); + cuvsCagraIndexParamsCreate(&index_params); + cuvsCagraIndexCreate(&index); + + cuvsCagraBuild(res, index_params, dataset, index); + + cuvsCagraIndexDestroy(index); + cuvsCagraIndexParamsDestroy(index_params); + cuvsResourcesDestroy(res); + +C++ +^^^ + +.. code-block:: c++ + + #include + + using namespace cuvs::neighbors; + + raft::device_matrix_view dataset = load_dataset(); + raft::device_resources res; + + cagra::index_params index_params; + + auto index = cagra::build(res, index_params, dataset); + + +Python +^^^^^^ + +.. code-block:: python + + from cuvs.neighbors import cagra + + dataset = load_data() + index_params = cagra.IndexParams() + + index = cagra.build_index(build_params, dataset) + +Rust +^^^^ + +.. code-block:: rust + + use cuvs::cagra::{Index, IndexParams}; + use cuvs::{Resources, Result}; + + use ndarray_rand::rand_distr::Uniform; + use ndarray_rand::RandomExt; + + /// Example showing how to index and search data with CAGRA + fn cagra_example() -> Result<()> { + let res = Resources::new()?; + + // Create a new random dataset to index + let n_datapoints = 65536; + let n_features = 512; + let dataset = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + // build the cagra index + let build_params = IndexParams::new()?; + let index = Index::build(&res, &build_params, &dataset)?; + + Ok(()) + } + + +Searching an index +------------------ + + +CPU/GPU interoperability +------------------------ + +Serializing an index +-------------------- \ No newline at end of file From 6eb9bdb4c92f2248c5e11be891ab75d0e7a70e16 Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Wed, 13 Mar 2024 08:38:21 -0500 Subject: [PATCH 013/623] Add upper bound to prevent usage of NumPy 2 (#52) NumPy 2 is expected to be released in the near future. For the RAPIDS 24.04 release, we will pin to `numpy>=1.23,<2.0a0`. This PR adds an upper bound to affected RAPIDS repositories. xref: https://github.com/rapidsai/build-planning/issues/29 Authors: - Bradley Dice (https://github.com/bdice) Approvers: - Divye Gala (https://github.com/divyegala) - Dante Gama Dessavre (https://github.com/dantegd) - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cuvs/pull/52 --- conda/environments/all_cuda-118_arch-aarch64.yaml | 2 +- conda/environments/all_cuda-118_arch-x86_64.yaml | 2 +- conda/environments/all_cuda-122_arch-aarch64.yaml | 2 +- conda/environments/all_cuda-122_arch-x86_64.yaml | 2 +- dependencies.yaml | 2 +- python/cuvs/pyproject.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/conda/environments/all_cuda-118_arch-aarch64.yaml b/conda/environments/all_cuda-118_arch-aarch64.yaml index e8854eae6a..897e200074 100644 --- a/conda/environments/all_cuda-118_arch-aarch64.yaml +++ b/conda/environments/all_cuda-118_arch-aarch64.yaml @@ -40,7 +40,7 @@ dependencies: - make - nccl>=2.9.9 - ninja -- numpy>=1.23 +- numpy>=1.23,<2.0a0 - numpydoc - nvcc_linux-aarch64=11.8 - pre-commit diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index f8269df7b9..92c9c2d601 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -40,7 +40,7 @@ dependencies: - make - nccl>=2.9.9 - ninja -- numpy>=1.23 +- numpy>=1.23,<2.0a0 - numpydoc - nvcc_linux-64=11.8 - pre-commit diff --git a/conda/environments/all_cuda-122_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml index e5940cbed8..0e8fa75a20 100644 --- a/conda/environments/all_cuda-122_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -37,7 +37,7 @@ dependencies: - make - nccl>=2.9.9 - ninja -- numpy>=1.23 +- numpy>=1.23,<2.0a0 - numpydoc - pre-commit - pydata-sphinx-theme diff --git a/conda/environments/all_cuda-122_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml index 1008c02700..06da6fecc0 100644 --- a/conda/environments/all_cuda-122_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -37,7 +37,7 @@ dependencies: - make - nccl>=2.9.9 - ninja -- numpy>=1.23 +- numpy>=1.23,<2.0a0 - numpydoc - pre-commit - pydata-sphinx-theme diff --git a/dependencies.yaml b/dependencies.yaml index fedf4946ee..ff3aa61b19 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -356,7 +356,7 @@ dependencies: common: - output_types: [conda, pyproject] packages: - - &numpy numpy>=1.23 + - &numpy numpy>=1.23,<2.0a0 - output_types: [conda] packages: - *rmm_conda diff --git a/python/cuvs/pyproject.toml b/python/cuvs/pyproject.toml index 07578abf3a..ae56a37acd 100644 --- a/python/cuvs/pyproject.toml +++ b/python/cuvs/pyproject.toml @@ -37,7 +37,7 @@ license = { text = "Apache 2.0" } requires-python = ">=3.9" dependencies = [ "cuda-python>=11.7.1,<12.0a0", - "numpy>=1.23", + "numpy>=1.23,<2.0a0", "pylibraft==24.4.*", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ From 5b8393fd3de36ef8f81320bf7259630a2095e8a5 Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Thu, 14 Mar 2024 21:54:55 +0100 Subject: [PATCH 014/623] More docs updates for 24.04 (#54) Authors: - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Divye Gala (https://github.com/divyegala) URL: https://github.com/rapidsai/cuvs/pull/54 --- README.md | 12 +- docs/source/working_with_ann_indexes.rst | 107 ++---------------- docs/source/working_with_ann_indexes_c.rst | 39 +++++++ docs/source/working_with_ann_indexes_cpp.rst | 35 ++++++ .../working_with_ann_indexes_python.rst | 30 +++++ docs/source/working_with_ann_indexes_rust.rst | 43 +++++++ 6 files changed, 162 insertions(+), 104 deletions(-) create mode 100644 docs/source/working_with_ann_indexes_c.rst create mode 100644 docs/source/working_with_ann_indexes_cpp.rst create mode 100644 docs/source/working_with_ann_indexes_python.rst create mode 100644 docs/source/working_with_ann_indexes_rust.rst diff --git a/README.md b/README.md index 4a2d2de166..56aaec0791 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ #
 cuVS: Vector Search and Clustering on the GPU
+> [!note] +> cuVS is a new library mostly derived from the approximate nearest neighbors and clustering algorithms in the [RAPIDS RAFT](https://github.com/rapidsai) library of data mining primitives. RAPIDS RAFT currently contains the most fully-featured versions of the approximate nearest neighbors and clustering algorithms in cuVS. We are in the process of migrating the algorithms from RAFT to cuVS, but if you are unsure of which to use, please consider the following: +> 1. RAFT contains C++ and Python APIs for all of the approximate nearest neighbors and clustering algorithms. +> 2. cuVS contains a growing support for different languages, including C, C++, Python, and Rust. We will be adding more language support to cuVS in the future but will not be improving the language support for RAFT. +> 3. Once all of RAFT's approximate nearest neighbors and clustering algorithms are moved to cuVS, the RAFT APIs will be deprecated and eventually removed altogether. Once removed, RAFT will become a lightweight header-only library. In the meantime, there's no harm in using RAFT if support for additional languages is not needed. + + ## Contents 1. [Useful Resources](#useful-resources) @@ -23,11 +30,6 @@ cuVS contains state-of-the-art implementations of several algorithms for running approximate nearest neighbors and clustering on the GPU. It can be used directly or through the various databases and other libraries that have integrated it. The primary goal of cuVS is to simplify the use of GPUs for vector similarity search and clustering. -**Please note** that cuVS is a new library mostly derived from the approximate nearest neighbors and clustering algorithms in the [RAPIDS RAFT](https://github.com/rapidsai) library of data mining primitives. RAPIDS RAFT currently contains the most fully-featured versions of the approximate nearest neighbors and clustering algorithms in cuVS. We are in the process of migrating the algorithms from RAFT to cuVS, but if you are unsure of which to use, please consider the following: -1. RAFT contains C++ and Python APIs for all of the approximate nearest neighbors and clustering algorithms. -2. cuVS contains a growing support for different languages, including C, C++, Python, and Rust. We will be adding more language support to cuVS in the future but will not be improving the language support for RAFT. -3. Once all of RAFT's approximate nearest neighbors and clustering algorithms are moved to cuVS, the RAFT APIs will be deprecated and eventually removed altogether. Once removed, RAFT will become a lightweight header-only library. In the meantime, there's no harm in using RAFT if support for additional languages is not needed. - ## Installing cuVS cuVS comes with pre-built packages that can be installed through [conda](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html#managing-python). Different packages are available for the different languages supported by cuVS: diff --git a/docs/source/working_with_ann_indexes.rst b/docs/source/working_with_ann_indexes.rst index f347a2f428..8e91fb4acd 100644 --- a/docs/source/working_with_ann_indexes.rst +++ b/docs/source/working_with_ann_indexes.rst @@ -1,102 +1,11 @@ Working with ANN Indexes ======================== -- `Building an index`_ -- `Searching an index`_ -- `CPU/GPU Interoperability`_ -- `Serializing an index`_ - -Building an index ------------------ - -C -^ - -.. code-block:: c - - #include - - cuvsResources_t res; - cuvsCagraIndexParams_t index_params; - cuvsCagraIndex_t index; - - DLManagedTensor *dataset; - load_dataset(dataset); - - cuvsResourcesCreate(&res); - cuvsCagraIndexParamsCreate(&index_params); - cuvsCagraIndexCreate(&index); - - cuvsCagraBuild(res, index_params, dataset, index); - - cuvsCagraIndexDestroy(index); - cuvsCagraIndexParamsDestroy(index_params); - cuvsResourcesDestroy(res); - -C++ -^^^ - -.. code-block:: c++ - - #include - - using namespace cuvs::neighbors; - - raft::device_matrix_view dataset = load_dataset(); - raft::device_resources res; - - cagra::index_params index_params; - - auto index = cagra::build(res, index_params, dataset); - - -Python -^^^^^^ - -.. code-block:: python - - from cuvs.neighbors import cagra - - dataset = load_data() - index_params = cagra.IndexParams() - - index = cagra.build_index(build_params, dataset) - -Rust -^^^^ - -.. code-block:: rust - - use cuvs::cagra::{Index, IndexParams}; - use cuvs::{Resources, Result}; - - use ndarray_rand::rand_distr::Uniform; - use ndarray_rand::RandomExt; - - /// Example showing how to index and search data with CAGRA - fn cagra_example() -> Result<()> { - let res = Resources::new()?; - - // Create a new random dataset to index - let n_datapoints = 65536; - let n_features = 512; - let dataset = - ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); - - // build the cagra index - let build_params = IndexParams::new()?; - let index = Index::build(&res, &build_params, &dataset)?; - - Ok(()) - } - - -Searching an index ------------------- - - -CPU/GPU interoperability ------------------------- - -Serializing an index --------------------- \ No newline at end of file +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + working_with_ann_indexes_c.rst + working_with_ann_indexes_cpp.rst + working_with_ann_indexes_python.rst + working_with_ann_indexes_rust.rst diff --git a/docs/source/working_with_ann_indexes_c.rst b/docs/source/working_with_ann_indexes_c.rst new file mode 100644 index 0000000000..6632e1c96e --- /dev/null +++ b/docs/source/working_with_ann_indexes_c.rst @@ -0,0 +1,39 @@ +Working with ANN Indexes in C +============================= + +- `Building an index`_ +- `Searching an index`_ +- `CPU/GPU Interoperability`_ +- `Serializing an index`_ + +Building an index +----------------- + +.. code-block:: c + + #include + + cuvsResources_t res; + cuvsCagraIndexParams_t index_params; + cuvsCagraIndex_t index; + + DLManagedTensor *dataset; + load_dataset(dataset); + + cuvsResourcesCreate(&res); + cuvsCagraIndexParamsCreate(&index_params); + cuvsCagraIndexCreate(&index); + + cuvsCagraBuild(res, index_params, dataset, index); + + cuvsCagraIndexDestroy(index); + cuvsCagraIndexParamsDestroy(index_params); + cuvsResourcesDestroy(res); + + +Searching an index +------------------ + + +Serializing an index +-------------------- \ No newline at end of file diff --git a/docs/source/working_with_ann_indexes_cpp.rst b/docs/source/working_with_ann_indexes_cpp.rst new file mode 100644 index 0000000000..a74b4c88c1 --- /dev/null +++ b/docs/source/working_with_ann_indexes_cpp.rst @@ -0,0 +1,35 @@ +Working with ANN Indexes in C++ +=============================== + +- `Building an index`_ +- `Searching an index`_ +- `CPU/GPU Interoperability`_ +- `Serializing an index`_ + +Building an index +----------------- + +.. code-block:: c++ + + #include + + using namespace cuvs::neighbors; + + raft::device_matrix_view dataset = load_dataset(); + raft::device_resources res; + + cagra::index_params index_params; + + auto index = cagra::build(res, index_params, dataset); + + + +Searching an index +------------------ + + +CPU/GPU interoperability +------------------------ + +Serializing an index +-------------------- \ No newline at end of file diff --git a/docs/source/working_with_ann_indexes_python.rst b/docs/source/working_with_ann_indexes_python.rst new file mode 100644 index 0000000000..bc439bbd5b --- /dev/null +++ b/docs/source/working_with_ann_indexes_python.rst @@ -0,0 +1,30 @@ +Working with ANN Indexes in Python +================================== + +- `Building an index`_ +- `Searching an index`_ +- `CPU/GPU Interoperability`_ +- `Serializing an index`_ + +Building an index +----------------- + +.. code-block:: python + + from cuvs.neighbors import cagra + + dataset = load_data() + index_params = cagra.IndexParams() + + index = cagra.build_index(build_params, dataset) + + +Searching an index +------------------ + + +CPU/GPU interoperability +------------------------ + +Serializing an index +-------------------- \ No newline at end of file diff --git a/docs/source/working_with_ann_indexes_rust.rst b/docs/source/working_with_ann_indexes_rust.rst new file mode 100644 index 0000000000..e7da4960a8 --- /dev/null +++ b/docs/source/working_with_ann_indexes_rust.rst @@ -0,0 +1,43 @@ +Working with ANN Indexes in Rust +================================ + +- `Building an index`_ +- `Searching an index`_ +- `CPU/GPU Interoperability`_ +- `Serializing an index`_ + +Building an index +----------------- + +.. code-block:: rust + + use cuvs::cagra::{Index, IndexParams}; + use cuvs::{Resources, Result}; + + use ndarray_rand::rand_distr::Uniform; + use ndarray_rand::RandomExt; + + /// Example showing how to index and search data with CAGRA + fn cagra_example() -> Result<()> { + let res = Resources::new()?; + + // Create a new random dataset to index + let n_datapoints = 65536; + let n_features = 512; + let dataset = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + // build the cagra index + let build_params = IndexParams::new()?; + let index = Index::build(&res, &build_params, &dataset)?; + + Ok(()) + } + + +Searching an index +------------------ + + +Serializing an index +-------------------- \ No newline at end of file From b4e6a9eb6a304baf3d722b2c78bba9b73bd31b57 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Thu, 14 Mar 2024 13:56:22 -0700 Subject: [PATCH 015/623] Update build.sh to build rust bindings (#53) * Update build.sh to build the rust bindings * update ci/release/update-version.sh to keep the rust version up to date * remove references to the cuvs-rs conda package from the README * Remove temporary copy of rust documentation from `docs/source/_static` - just copy directly to output folder Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/53 --- README.md | 8 ++++---- build.sh | 21 +++++++++++++++------ ci/build_rust.sh | 5 ++--- ci/release/update-version.sh | 7 ++++++- docs/source/build.rst | 6 ++++++ rust/Cargo.toml | 2 +- 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 56aaec0791..160f466042 100755 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ cuVS contains state-of-the-art implementations of several algorithms for running cuVS comes with pre-built packages that can be installed through [conda](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html#managing-python). Different packages are available for the different languages supported by cuVS: -| Python | C++ | C | Rust | -|--------|-----|---|------| -| `pycuvs`| `libcuvs` | `libcuvs_c` | `cuvs-rs` | +| Python | C++ | C | +|--------|-----|---| +| `pycuvs`| `libcuvs` | `libcuvs_c` | ### Stable release @@ -172,7 +172,7 @@ fn cagra_example() -> Result<()> { ## Contributing -If you are interested in contributing to the cuVS library, please read our [Contributing guidelines](docs/source/contributing.md). Refer to the [Developer Guide](docs/source/developer_guide.md) for details on the developer guidelines, workflows, and principals. +If you are interested in contributing to the cuVS library, please read our [Contributing guidelines](docs/source/contributing.md). Refer to the [Developer Guide](docs/source/developer_guide.md) for details on the developer guidelines, workflows, and principles. ## References diff --git a/build.sh b/build.sh index 8c4ece05e4..0c987ed962 100755 --- a/build.sh +++ b/build.sh @@ -18,13 +18,14 @@ ARGS=$* # scripts, and that this script resides in the repo dir! REPODIR=$(cd $(dirname $0); pwd) -VALIDARGS="clean libcuvs python docs tests examples clean --uninstall -v -g -n --compile-static-lib --allgpuarch --no-nvtx --show_depr_warn --incl-cache-stats --time -h" +VALIDARGS="clean libcuvs python rust docs tests examples --uninstall -v -g -n --compile-static-lib --allgpuarch --no-nvtx --show_depr_warn --incl-cache-stats --time -h" HELP="$0 [ ...] [ ...] [--cmake-args=\"\"] [--cache-tool=] [--limit-tests=] [--build-metrics=] where is: clean - remove all existing build artifacts and configuration (start over) libcuvs - build the cuvs C++ code only. Also builds the C-wrapper library around the C++ code. python - build the cuvs Python package + rust - build the cuvs Rust bindings docs - build the documentation tests - build the tests examples - build the examples @@ -54,8 +55,8 @@ LIBCUVS_BUILD_DIR=${LIBCUVS_BUILD_DIR:=${REPODIR}/cpp/build} SPHINX_BUILD_DIR=${REPODIR}/docs DOXYGEN_BUILD_DIR=${REPODIR}/cpp/doxygen PYTHON_BUILD_DIR=${REPODIR}/python/cuvs/_skbuild -RUST_BUILD_DIR=${REPODIR}/rust -BUILD_DIRS="${LIBCUVS_BUILD_DIR} ${PYTHON_BUILD_DIR}" +RUST_BUILD_DIR=${REPODIR}/rust/target +BUILD_DIRS="${LIBCUVS_BUILD_DIR} ${PYTHON_BUILD_DIR} ${RUST_BUILD_DIR}" # Set defaults for vars modified by flags to this script CMAKE_LOG_LEVEL="" @@ -77,6 +78,7 @@ CLEAN=0 UNINSTALL=0 DISABLE_DEPRECATION_WARNINGS=ON CMAKE_TARGET="" +EXTRA_CMAKE_ARGS="" # Set defaults for vars that may not have been defined externally INSTALL_PREFIX=${INSTALL_PREFIX:=${PREFIX:=${CONDA_PREFIX:=$LIBCUVS_BUILD_DIR/install}}} @@ -392,6 +394,13 @@ if (( ${NUMARGS} == 0 )) || hasArg python; then python -m pip install --no-build-isolation --no-deps -vvv ${REPODIR}/python/cuvs fi +# Build the cuvs Rust bindings +if (( ${NUMARGS} == 0 )) || hasArg rust; then + cd ${REPODIR}/rust + cargo build --examples --lib + cargo test +fi + export RAPIDS_VERSION="$(sed -E -e 's/^([0-9]{2})\.([0-9]{2})\.([0-9]{2}).*$/\1.\2.\3/' "${REPODIR}/VERSION")" export RAPIDS_VERSION_MAJOR_MINOR="$(sed -E -e 's/^([0-9]{2})\.([0-9]{2})\.([0-9]{2}).*$/\1.\2/' "${REPODIR}/VERSION")" @@ -399,11 +408,11 @@ if hasArg docs; then set -x cd ${DOXYGEN_BUILD_DIR} doxygen Doxyfile - cd ${RUST_BUILD_DIR} - cargo doc -p cuvs --no-deps - rsync -av ${RUST_BUILD_DIR}/target/doc/ ${SPHINX_BUILD_DIR}/source/_static/rust cd ${SPHINX_BUILD_DIR} sphinx-build -b html source _html + cd ${REPODIR}/rust + cargo doc -p cuvs --no-deps + rsync -av ${RUST_BUILD_DIR}/doc/ ${SPHINX_BUILD_DIR}/_html/_static/rust fi ################################################################################ diff --git a/ci/build_rust.sh b/ci/build_rust.sh index 895dd41e07..59f0924c80 100755 --- a/ci/build_rust.sh +++ b/ci/build_rust.sh @@ -35,6 +35,5 @@ rapids-mamba-retry install \ libcuvs \ libraft -# build and test the rust bindings -cd rust -cargo test +export EXTRA_CMAKE_ARGS="" +bash ./build.sh rust diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index 7dc4d12738..589110ab12 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -1,7 +1,7 @@ #!/bin/bash # Copyright (c) 2020-2024, NVIDIA CORPORATION. ######################## -# RAFT Version Updater # +# CUVS Version Updater # ######################## ## Usage @@ -21,6 +21,7 @@ CURRENT_SHORT_TAG=${CURRENT_MAJOR}.${CURRENT_MINOR} #Get . for next version NEXT_MAJOR=$(echo $NEXT_FULL_TAG | awk '{split($0, a, "."); print a[1]}') NEXT_MINOR=$(echo $NEXT_FULL_TAG | awk '{split($0, a, "."); print a[2]}') +NEXT_PATCH=$(echo $NEXT_FULL_TAG | awk '{split($0, a, "."); print a[3]}') NEXT_SHORT_TAG=${NEXT_MAJOR}.${NEXT_MINOR} NEXT_UCX_PY_SHORT_TAG="$(curl -sL https://version.gpuci.io/rapids/${NEXT_SHORT_TAG})" NEXT_UCX_PY_VERSION="${NEXT_UCX_PY_SHORT_TAG}.*" @@ -81,6 +82,10 @@ sed_runner "s|:[0-9][0-9].[0-9][0-9]|:${NEXT_SHORT_TAG}|g" docs/source/raft_ann_ sed_runner "s|branch-[0-9][0-9].[0-9][0-9]|branch-${NEXT_SHORT_TAG}|g" README.md +# rust can't handle leading 0's in the major/minor/patch version - remove +NEXT_FULL_RUST_TAG=$(printf "%d.%d.%d" $((10#$NEXT_MAJOR)) $((10#$NEXT_MINOR)) $((10#$NEXT_PATCH))) +sed_runner "s/version = \".*\"/version = \"${NEXT_FULL_RUST_TAG}\"/g" rust/Cargo.toml + # .devcontainer files find .devcontainer/ -type f -name devcontainer.json -print0 | while IFS= read -r -d '' filename; do sed_runner "s@rapidsai/devcontainers:[0-9.]*@rapidsai/devcontainers:${NEXT_SHORT_TAG}@g" "${filename}" diff --git a/docs/source/build.rst b/docs/source/build.rst index 8140224656..dbd882660f 100644 --- a/docs/source/build.rst +++ b/docs/source/build.rst @@ -168,6 +168,12 @@ The Python packages can also be uninstalled using the `build.sh` script: Rust library ^^^^^^^^^^^^ +The Rust bindings can be built with + +.. code-block:: bash + + ./build.sh rust + Using CMake directly ^^^^^^^^^^^^^^^^^^^^ diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7e9bfe4ecb..689fd0565c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,7 +6,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.0" +version = "24.4.0" edition = "2021" repository = "https://github.com/rapidsai/cuvs" homepage = "https://github.com/rapidsai/cuvs" From 0f11d07f65b597c3397000b743e18ac13db26d49 Mon Sep 17 00:00:00 2001 From: Ray Douglass Date: Mon, 18 Mar 2024 09:27:42 -0400 Subject: [PATCH 016/623] DOC v24.06 Updates [skip ci] --- .../cuda11.8-conda/devcontainer.json | 4 ++-- .devcontainer/cuda11.8-pip/devcontainer.json | 6 ++--- .../cuda12.0-conda/devcontainer.json | 4 ++-- .devcontainer/cuda12.0-pip/devcontainer.json | 6 ++--- .github/workflows/build.yaml | 14 +++++------ .github/workflows/pr.yaml | 24 +++++++++---------- .github/workflows/test.yaml | 8 +++---- README.md | 2 +- VERSION | 2 +- .../all_cuda-118_arch-aarch64.yaml | 4 ++-- .../all_cuda-118_arch-x86_64.yaml | 4 ++-- .../all_cuda-122_arch-aarch64.yaml | 4 ++-- .../all_cuda-122_arch-x86_64.yaml | 4 ++-- dependencies.yaml | 12 +++++----- docs/source/developer_guide.md | 8 +++---- .../cpp/cmake/thirdparty/fetch_rapids.cmake | 2 +- python/cuvs/pyproject.toml | 6 ++--- rust/Cargo.toml | 2 +- 18 files changed, 58 insertions(+), 58 deletions(-) diff --git a/.devcontainer/cuda11.8-conda/devcontainer.json b/.devcontainer/cuda11.8-conda/devcontainer.json index cefbea72b7..bea16385a0 100644 --- a/.devcontainer/cuda11.8-conda/devcontainer.json +++ b/.devcontainer/cuda11.8-conda/devcontainer.json @@ -5,12 +5,12 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "conda", - "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda11.8-mambaforge-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.06-cpp-llvm16-cuda11.8-mambaforge-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" diff --git a/.devcontainer/cuda11.8-pip/devcontainer.json b/.devcontainer/cuda11.8-pip/devcontainer.json index 05518805aa..39b7971e79 100644 --- a/.devcontainer/cuda11.8-pip/devcontainer.json +++ b/.devcontainer/cuda11.8-pip/devcontainer.json @@ -5,13 +5,13 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda11.8-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.06-cpp-llvm16-cuda11.8-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/ucx:24.2": {"version": "1.14.1"}, - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": {"version": "1.14.1"}, + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/ucx", diff --git a/.devcontainer/cuda12.0-conda/devcontainer.json b/.devcontainer/cuda12.0-conda/devcontainer.json index 3f89836e26..4d9cbf33d8 100644 --- a/.devcontainer/cuda12.0-conda/devcontainer.json +++ b/.devcontainer/cuda12.0-conda/devcontainer.json @@ -5,12 +5,12 @@ "args": { "CUDA": "12.0", "PYTHON_PACKAGE_MANAGER": "conda", - "BASE": "rapidsai/devcontainers:24.04-cpp-mambaforge-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.06-cpp-mambaforge-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" diff --git a/.devcontainer/cuda12.0-pip/devcontainer.json b/.devcontainer/cuda12.0-pip/devcontainer.json index 33f67cd3fb..2a77539cff 100644 --- a/.devcontainer/cuda12.0-pip/devcontainer.json +++ b/.devcontainer/cuda12.0-pip/devcontainer.json @@ -5,13 +5,13 @@ "args": { "CUDA": "12.0", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda12.0-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.06-cpp-llvm16-cuda12.0-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/ucx:24.2": {"version": "1.14.1"}, - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": {"version": "1.14.1"}, + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/ucx", diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 61ebdf0bca..66945cb2ae 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,7 @@ concurrency: jobs: cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.06 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -37,7 +37,7 @@ jobs: rust-build: needs: cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.06 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -50,7 +50,7 @@ jobs: python-build: needs: [cpp-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.06 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -59,7 +59,7 @@ jobs: upload-conda: needs: [cpp-build, python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.06 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -70,7 +70,7 @@ jobs: if: github.ref_type == 'branch' needs: python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.06 with: arch: "amd64" branch: ${{ inputs.branch }} @@ -82,7 +82,7 @@ jobs: sha: ${{ inputs.sha }} wheel-build-cuvs: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.06 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -92,7 +92,7 @@ jobs: wheel-publish-cuvs: needs: wheel-build-cuvs secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.06 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 7c6db2c603..652092dde5 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -24,29 +24,29 @@ jobs: - wheel-tests-cuvs - devcontainer secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.06 checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.06 with: enable_check_generated_files: false conda-cpp-build: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.06 with: build_type: pull-request node_type: cpu16 conda-cpp-tests: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.06 with: build_type: pull-request conda-cpp-checks: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.06 with: build_type: pull-request enable_check_symbols: true @@ -54,19 +54,19 @@ jobs: conda-python-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.06 with: build_type: pull-request conda-python-tests: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.06 with: build_type: pull-request docs-build: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.06 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -76,7 +76,7 @@ jobs: rust-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.06 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -86,20 +86,20 @@ jobs: wheel-build-cuvs: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.06 with: build_type: pull-request script: ci/build_wheel_cuvs.sh wheel-tests-cuvs: needs: wheel-build-cuvs secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.06 with: build_type: pull-request script: ci/test_wheel_cuvs.sh devcontainer: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.06 with: build_command: | sccache -z; diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0e66113f24..4f49e87cfe 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ on: jobs: conda-cpp-checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-post-build-checks.yaml@branch-24.06 with: build_type: nightly branch: ${{ inputs.branch }} @@ -26,7 +26,7 @@ jobs: symbol_exclusions: (void (thrust::|cub::)|_ZN\d+raft_cutlass) conda-cpp-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@branch-24.06 with: build_type: nightly branch: ${{ inputs.branch }} @@ -34,7 +34,7 @@ jobs: sha: ${{ inputs.sha }} conda-python-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.06 with: build_type: nightly branch: ${{ inputs.branch }} @@ -42,7 +42,7 @@ jobs: sha: ${{ inputs.sha }} wheel-tests-cuvs: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.04 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.06 with: build_type: nightly branch: ${{ inputs.branch }} diff --git a/README.md b/README.md index 160f466042..c787d4ded0 100755 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ mamba install -c conda-forge -c nvidia -c rapidsai pycuvs ### Nightlies If installing a version that has not yet been released, the `rapidsai` channel can be replaced with `rapidsai-nightly`: ```bash -mamba install -c conda-forge -c nvidia -c rapidsai-nightly pycuvs=24.04* +mamba install -c conda-forge -c nvidia -c rapidsai-nightly pycuvs=24.06 ``` Please see the [Build and Install Guide](docs/source/build.md) for more information on installing cuVS and building from source. diff --git a/VERSION b/VERSION index 4a2fe8aa57..0bff6981a3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -24.04.00 +24.06.00 diff --git a/conda/environments/all_cuda-118_arch-aarch64.yaml b/conda/environments/all_cuda-118_arch-aarch64.yaml index 897e200074..364d9c76af 100644 --- a/conda/environments/all_cuda-118_arch-aarch64.yaml +++ b/conda/environments/all_cuda-118_arch-aarch64.yaml @@ -45,11 +45,11 @@ dependencies: - nvcc_linux-aarch64=11.8 - pre-commit - pydata-sphinx-theme -- pylibraft==24.4.* +- pylibraft==24.6.* - pytest-cov - pytest==7.* - recommonmark -- rmm==24.4.* +- rmm==24.6.* - rust - scikit-build-core>=0.7.0 - scikit-learn diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 92c9c2d601..f269ff55ea 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -45,11 +45,11 @@ dependencies: - nvcc_linux-64=11.8 - pre-commit - pydata-sphinx-theme -- pylibraft==24.4.* +- pylibraft==24.6.* - pytest-cov - pytest==7.* - recommonmark -- rmm==24.4.* +- rmm==24.6.* - rust - scikit-build-core>=0.7.0 - scikit-learn diff --git a/conda/environments/all_cuda-122_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml index 0e8fa75a20..d232a16318 100644 --- a/conda/environments/all_cuda-122_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -41,11 +41,11 @@ dependencies: - numpydoc - pre-commit - pydata-sphinx-theme -- pylibraft==24.4.* +- pylibraft==24.6.* - pytest-cov - pytest==7.* - recommonmark -- rmm==24.4.* +- rmm==24.6.* - rust - scikit-build-core>=0.7.0 - scikit-learn diff --git a/conda/environments/all_cuda-122_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml index 06da6fecc0..a5dc50d762 100644 --- a/conda/environments/all_cuda-122_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -41,11 +41,11 @@ dependencies: - numpydoc - pre-commit - pydata-sphinx-theme -- pylibraft==24.4.* +- pylibraft==24.6.* - pytest-cov - pytest==7.* - recommonmark -- rmm==24.4.* +- rmm==24.6.* - rust - scikit-build-core>=0.7.0 - scikit-learn diff --git a/dependencies.yaml b/dependencies.yaml index ff3aa61b19..1951ee5cf0 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -140,8 +140,8 @@ dependencies: common: - output_types: [conda] packages: - - &rmm_conda rmm==24.4.* - - &pylibraft_conda pylibraft==24.4.* + - &rmm_conda rmm==24.6.* + - &pylibraft_conda pylibraft==24.6.* - dlpack>=0.8,<1.0 - output_types: requirements packages: @@ -163,12 +163,12 @@ dependencies: matrices: - matrix: {cuda: "12.*"} packages: - - &rmm_cu12 rmm-cu12==24.4.* - - &pylibraft_cu12 pylibraft-cu12==24.4.* + - &rmm_cu12 rmm-cu12==24.6.* + - &pylibraft_cu12 pylibraft-cu12==24.6.* - matrix: {cuda: "11.*"} packages: - - &rmm_cu11 rmm-cu11==24.4.* - - &pylibraft_cu11 pylibraft-cu11==24.4.* + - &rmm_cu11 rmm-cu11==24.6.* + - &pylibraft_cu11 pylibraft-cu11==24.6.* - {matrix: null, packages: [*rmm_conda, *pylibraft_conda] } checks: common: diff --git a/docs/source/developer_guide.md b/docs/source/developer_guide.md index d29130add0..e10e8987af 100644 --- a/docs/source/developer_guide.md +++ b/docs/source/developer_guide.md @@ -187,7 +187,7 @@ RAFT relies on `clang-format` to enforce code style across all C++ and CUDA sour 1. Do not split empty functions/records/namespaces. 2. Two-space indentation everywhere, including the line continuations. 3. Disable reflowing of comments. - The reasons behind these deviations from the Google style guide are given in comments [here](https://github.com/rapidsai/raft/blob/branch-24.04/cpp/.clang-format). + The reasons behind these deviations from the Google style guide are given in comments [here](https://github.com/rapidsai/raft/blob/branch-24.06/cpp/.clang-format). [`doxygen`](https://doxygen.nl/) is used as documentation generator and also as a documentation linter. In order to run doxygen as a linter on C++/CUDA code, run @@ -205,7 +205,7 @@ you can run `codespell -i 3 -w .` from the repository root directory. This will bring up an interactive prompt to select which spelling fixes to apply. ### #include style -[include_checker.py](https://github.com/rapidsai/raft/blob/branch-24.04/cpp/scripts/include_checker.py) is used to enforce the include style as follows: +[include_checker.py](https://github.com/rapidsai/raft/blob/branch-24.06/cpp/scripts/include_checker.py) is used to enforce the include style as follows: 1. `#include "..."` should be used for referencing local files only. It is acceptable to be used for referencing files in a sub-folder/parent-folder of the same algorithm, but should never be used to include files in other algorithms or between algorithms and the primitives or other dependencies. 2. `#include <...>` should be used for referencing everything else @@ -215,7 +215,7 @@ python ./cpp/scripts/include_checker.py --inplace [cpp/include cpp/test ... list ``` ### Copyright header -[copyright.py](https://github.com/rapidsai/raft/blob/branch-24.04/ci/checks/copyright.py) checks the Copyright header for all git-modified files +[copyright.py](https://github.com/rapidsai/raft/blob/branch-24.06/ci/checks/copyright.py) checks the Copyright header for all git-modified files Manually, you can run the following to bulk-fix the header if only the years need to be updated: ```bash @@ -229,7 +229,7 @@ Call CUDA APIs via the provided helper macros `RAFT_CUDA_TRY`, `RAFT_CUBLAS_TRY` ## Logging ### Introduction -Anything and everything about logging is defined inside [logger.hpp](https://github.com/rapidsai/raft/blob/branch-24.04/cpp/include/raft/core/logger.hpp). It uses [spdlog](https://github.com/gabime/spdlog) underneath, but this information is transparent to all. +Anything and everything about logging is defined inside [logger.hpp](https://github.com/rapidsai/raft/blob/branch-24.06/cpp/include/raft/core/logger.hpp). It uses [spdlog](https://github.com/gabime/spdlog) underneath, but this information is transparent to all. ### Usage ```cpp diff --git a/examples/cpp/cmake/thirdparty/fetch_rapids.cmake b/examples/cpp/cmake/thirdparty/fetch_rapids.cmake index c22f586cad..ec0e0816db 100644 --- a/examples/cpp/cmake/thirdparty/fetch_rapids.cmake +++ b/examples/cpp/cmake/thirdparty/fetch_rapids.cmake @@ -12,7 +12,7 @@ # the License. # Use this variable to update RAPIDS and RAFT versions -set(RAPIDS_VERSION "24.04") +set(RAPIDS_VERSION "24.06") if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/RAFT_RAPIDS.cmake) file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-${RAPIDS_VERSION}/RAPIDS.cmake diff --git a/python/cuvs/pyproject.toml b/python/cuvs/pyproject.toml index ae56a37acd..d3eae51ea5 100644 --- a/python/cuvs/pyproject.toml +++ b/python/cuvs/pyproject.toml @@ -19,8 +19,8 @@ requires = [ "cuda-python>=11.7.1,<12.0a0", "cython>=3.0.0", "ninja", - "pylibraft==24.4.*", - "rmm==24.4.*", + "pylibraft==24.6.*", + "rmm==24.6.*", "scikit-build-core[pyproject]>=0.7.0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. build-backend = "scikit_build_core.build" @@ -38,7 +38,7 @@ requires-python = ">=3.9" dependencies = [ "cuda-python>=11.7.1,<12.0a0", "numpy>=1.23,<2.0a0", - "pylibraft==24.4.*", + "pylibraft==24.6.*", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ "Intended Audience :: Developers", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 689fd0565c..700b65b40e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,7 +6,7 @@ members = [ resolver = "2" [workspace.package] -version = "24.4.0" +version = "24.6.0" edition = "2021" repository = "https://github.com/rapidsai/cuvs" homepage = "https://github.com/rapidsai/cuvs" From 964f68600e2be7f4f0c566ba7cff7c8d340187e2 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Mon, 18 Mar 2024 12:24:24 -0400 Subject: [PATCH 017/623] Replace local copyright check with pre-commit-hooks verify-copyright (#48) The local `copyright.py` script is bug-prone. Replace it with a more robust centralized script from `pre-commit-hooks`. Issue: https://github.com/rapidsai/build-planning/issues/30 Authors: - Kyle Edwards (https://github.com/KyleFromNVIDIA) Approvers: - Ben Frederickson (https://github.com/benfred) - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cuvs/pull/48 --- .pre-commit-config.yaml | 22 ++- ci/checks/copyright.py | 289 ---------------------------------------- 2 files changed, 16 insertions(+), 295 deletions(-) delete mode 100644 ci/checks/copyright.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a82fb74a57..ce5e8e0e4a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,12 +79,6 @@ repos: verbose: true require_serial: true exclude: .*/thirdparty/.* - - id: copyright-check - name: copyright-check - entry: python ./ci/checks/copyright.py --git-modified-only --update-current-year - language: python - pass_filenames: false - additional_dependencies: [gitpython] - id: include-check name: include-check entry: python ./cpp/scripts/include_checker.py cpp/bench cpp/include cpp/test @@ -107,6 +101,22 @@ repos: rev: v4.5.0 hooks: - id: check-json + - repo: https://github.com/rapidsai/pre-commit-hooks + rev: v0.0.1 + hooks: + - id: verify-copyright + files: | + (?x) + [.](cmake|cpp|cu|cuh|h|hpp|sh|pxd|py|pyx|rs)$| + CMakeLists[.]txt$| + CMakeLists_standalone[.]txt$| + meta[.]yaml$| + setup[.]cfg$ + exclude: | + (?x) + cpp/include/cuvs/neighbors/detail/faiss_select/| + docs/source/sphinxext/github_link\.py| + cpp/cmake/modules/FindAVX\.cmake| default_language_version: python: python3 diff --git a/ci/checks/copyright.py b/ci/checks/copyright.py deleted file mode 100644 index a49f27cd2c..0000000000 --- a/ci/checks/copyright.py +++ /dev/null @@ -1,289 +0,0 @@ -# Copyright (c) 2020-2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import datetime -import re -import argparse -import io -import os -import sys - -import git - -SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.expanduser(__file__))) - -# Add the scripts dir for gitutils -sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, - "../../cpp/scripts"))) - -# Now import gitutils. Ignore flake8 error here since there is no other way to -# set up imports -import gitutils # noqa: E402 - -FilesToCheck = [ - re.compile(r"[.](cmake|cpp|cu|cuh|h|hpp|sh|pxd|py|pyx)$"), - re.compile(r"CMakeLists[.]txt$"), - re.compile(r"CMakeLists_standalone[.]txt$"), - re.compile(r"setup[.]cfg$"), - re.compile(r"meta[.]yaml$") -] -ExemptFiles = [ - re.compile("cpp/include/cuvs/neighbors/detail/faiss_select/"), - re.compile("docs/source/sphinxext/github_link.py"), - re.compile("cpp/cmake/modules/FindAVX.cmake") -] - -# this will break starting at year 10000, which is probably OK :) -CheckSimple = re.compile( - r"Copyright *(?:\(c\))? *(\d{4}),? *NVIDIA C(?:ORPORATION|orporation)") -CheckDouble = re.compile( - r"Copyright *(?:\(c\))? *(\d{4})-(\d{4}),? *NVIDIA C(?:ORPORATION|orporation)" # noqa: E501 -) - - -def checkThisFile(f): - if isinstance(f, git.Diff): - if f.deleted_file or f.b_blob.size == 0: - return False - f = f.b_path - elif not os.path.exists(f) or os.stat(f).st_size == 0: - # This check covers things like symlinks which point to files that DNE - return False - for exempt in ExemptFiles: - if exempt.search(f): - return False - for checker in FilesToCheck: - if checker.search(f): - return True - return False - - -def modifiedFiles(): - """Get a set of all modified files, as Diff objects. - - The files returned have been modified in git since the merge base of HEAD - and the upstream of the target branch. We return the Diff objects so that - we can read only the staged changes. - """ - repo = git.Repo() - # Use the environment variable TARGET_BRANCH or RAPIDS_BASE_BRANCH (defined in CI) if possible - target_branch = os.environ.get("TARGET_BRANCH", os.environ.get("RAPIDS_BASE_BRANCH")) - if target_branch is None: - # Fall back to the closest branch if not on CI - target_branch = repo.git.describe( - all=True, tags=True, match="branch-*", abbrev=0 - ).lstrip("heads/") - - upstream_target_branch = None - if target_branch in repo.heads: - # Use the tracking branch of the local reference if it exists. This - # returns None if no tracking branch is set. - upstream_target_branch = repo.heads[target_branch].tracking_branch() - if upstream_target_branch is None: - # Fall back to the remote with the newest target_branch. This code - # path is used on CI because the only local branch reference is - # current-pr-branch, and thus target_branch is not in repo.heads. - # This also happens if no tracking branch is defined for the local - # target_branch. We use the remote with the latest commit if - # multiple remotes are defined. - candidate_branches = [ - remote.refs[target_branch] for remote in repo.remotes - if target_branch in remote.refs - ] - if len(candidate_branches) > 0: - upstream_target_branch = sorted( - candidate_branches, - key=lambda branch: branch.commit.committed_datetime, - )[-1] - else: - # If no remotes are defined, try to use the local version of the - # target_branch. If this fails, the repo configuration must be very - # strange and we can fix this script on a case-by-case basis. - upstream_target_branch = repo.heads[target_branch] - merge_base = repo.merge_base("HEAD", upstream_target_branch.commit)[0] - diff = merge_base.diff() - changed_files = {f for f in diff if f.b_path is not None} - return changed_files - - -def getCopyrightYears(line): - res = CheckSimple.search(line) - if res: - return int(res.group(1)), int(res.group(1)) - res = CheckDouble.search(line) - if res: - return int(res.group(1)), int(res.group(2)) - return None, None - - -def replaceCurrentYear(line, start, end): - # first turn a simple regex into double (if applicable). then update years - res = CheckSimple.sub(r"Copyright (c) \1-\1, NVIDIA CORPORATION", line) - res = CheckDouble.sub( - rf"Copyright (c) {start:04d}-{end:04d}, NVIDIA CORPORATION", - res, - ) - return res - - -def checkCopyright(f, update_current_year): - """Checks for copyright headers and their years.""" - errs = [] - thisYear = datetime.datetime.now().year - lineNum = 0 - crFound = False - yearMatched = False - - if isinstance(f, git.Diff): - path = f.b_path - lines = f.b_blob.data_stream.read().decode().splitlines(keepends=True) - else: - path = f - with open(f, encoding="utf-8") as fp: - lines = fp.readlines() - - for line in lines: - lineNum += 1 - start, end = getCopyrightYears(line) - if start is None: - continue - crFound = True - if start > end: - e = [ - path, - lineNum, - "First year after second year in the copyright " - "header (manual fix required)", - None, - ] - errs.append(e) - elif thisYear < start or thisYear > end: - e = [ - path, - lineNum, - "Current year not included in the copyright header", - None, - ] - if thisYear < start: - e[-1] = replaceCurrentYear(line, thisYear, end) - if thisYear > end: - e[-1] = replaceCurrentYear(line, start, thisYear) - errs.append(e) - else: - yearMatched = True - # copyright header itself not found - if not crFound: - e = [ - path, - 0, - "Copyright header missing or formatted incorrectly " - "(manual fix required)", - None, - ] - errs.append(e) - # even if the year matches a copyright header, make the check pass - if yearMatched: - errs = [] - - if update_current_year: - errs_update = [x for x in errs if x[-1] is not None] - if len(errs_update) > 0: - lines_changed = ", ".join(str(x[1]) for x in errs_update) - print(f"File: {path}. Changing line(s) {lines_changed}") - for _, lineNum, __, replacement in errs_update: - lines[lineNum - 1] = replacement - with open(path, "w", encoding="utf-8") as out_file: - out_file.writelines(lines) - - return errs - - -def getAllFilesUnderDir(root, pathFilter=None): - retList = [] - for dirpath, dirnames, filenames in os.walk(root): - for fn in filenames: - filePath = os.path.join(dirpath, fn) - if pathFilter(filePath): - retList.append(filePath) - return retList - - -def checkCopyright_main(): - """ - Checks for copyright headers in all the modified files. In case of local - repo, this script will just look for uncommitted files and in case of CI - it compares between branches "$PR_TARGET_BRANCH" and "current-pr-branch" - """ - retVal = 0 - - argparser = argparse.ArgumentParser( - "Checks for a consistent copyright header in git's modified files" - ) - argparser.add_argument( - "--update-current-year", - dest="update_current_year", - action="store_true", - required=False, - help="If set, " - "update the current year if a header is already " - "present and well formatted.", - ) - argparser.add_argument( - "--git-modified-only", - dest="git_modified_only", - action="store_true", - required=False, - help="If set, " - "only files seen as modified by git will be " - "processed.", - ) - - args, dirs = argparser.parse_known_args() - - if args.git_modified_only: - files = [f for f in modifiedFiles() if checkThisFile(f)] - else: - files = [] - for d in [os.path.abspath(d) for d in dirs]: - if not os.path.isdir(d): - raise ValueError(f"{d} is not a directory.") - files += getAllFilesUnderDir(d, pathFilter=checkThisFile) - - errors = [] - for f in files: - errors += checkCopyright(f, args.update_current_year) - - if len(errors) > 0: - if any(e[-1] is None for e in errors): - print("Copyright headers incomplete in some of the files!") - for e in errors: - print(" %s:%d Issue: %s" % (e[0], e[1], e[2])) - print("") - n_fixable = sum(1 for e in errors if e[-1] is not None) - path_parts = os.path.abspath(__file__).split(os.sep) - file_from_repo = os.sep.join(path_parts[path_parts.index("ci") :]) - if n_fixable > 0 and not args.update_current_year: - print( - f"You can run `python {file_from_repo} --git-modified-only " - "--update-current-year` and stage the results in git to " - f"fix {n_fixable} of these errors.\n" - ) - retVal = 1 - - return retVal - - -if __name__ == "__main__": - sys.exit(checkCopyright_main()) From e5d5e3a1e7577ee6c571ca397307872da9a72ce5 Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Tue, 19 Mar 2024 09:27:00 -0500 Subject: [PATCH 018/623] Remove unused get_*.cmake files. (#57) This PR removes some unused `get_*.cmake` files. I know cuVS may need to add some of these back as functionality is migrated from RAFT. As that happens, I would like to copy them again from the latest versions in RAFT. Specifically, files like `get_thrust.cmake` are outdated (we are moving to `rapids_cpm_cccl` and `rapids_cpm_thrust` is deprecated) so this will reduce the maintenance burden for CMake / build system developers in the interim period. Authors: - Bradley Dice (https://github.com/bdice) Approvers: - Micka (https://github.com/lowener) URL: https://github.com/rapidsai/cuvs/pull/57 --- cpp/cmake/thirdparty/get_fmt.cmake | 22 ------------ cpp/cmake/thirdparty/get_glog.cmake | 48 --------------------------- cpp/cmake/thirdparty/get_rmm.cmake | 23 ------------- cpp/cmake/thirdparty/get_spdlog.cmake | 33 ------------------ cpp/cmake/thirdparty/get_thrust.cmake | 24 -------------- 5 files changed, 150 deletions(-) delete mode 100644 cpp/cmake/thirdparty/get_fmt.cmake delete mode 100644 cpp/cmake/thirdparty/get_glog.cmake delete mode 100644 cpp/cmake/thirdparty/get_rmm.cmake delete mode 100644 cpp/cmake/thirdparty/get_spdlog.cmake delete mode 100644 cpp/cmake/thirdparty/get_thrust.cmake diff --git a/cpp/cmake/thirdparty/get_fmt.cmake b/cpp/cmake/thirdparty/get_fmt.cmake deleted file mode 100644 index c06f8a78bb..0000000000 --- a/cpp/cmake/thirdparty/get_fmt.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# ============================================================================= -# Copyright (c) 2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -# in compliance with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License -# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing permissions and limitations under -# the License. -# ============================================================================= - -# Use CPM to find or clone fmt -function(find_and_configure_fmt) - - include(${rapids-cmake-dir}/cpm/fmt.cmake) - rapids_cpm_fmt(INSTALL_EXPORT_SET rmm-exports BUILD_EXPORT_SET rmm-exports) -endfunction() - -find_and_configure_fmt() \ No newline at end of file diff --git a/cpp/cmake/thirdparty/get_glog.cmake b/cpp/cmake/thirdparty/get_glog.cmake deleted file mode 100644 index 56ed452746..0000000000 --- a/cpp/cmake/thirdparty/get_glog.cmake +++ /dev/null @@ -1,48 +0,0 @@ -#============================================================================= -# Copyright (c) 2021-2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#============================================================================= - -function(find_and_configure_glog) - set(oneValueArgs VERSION FORK PINNED_TAG EXCLUDE_FROM_ALL) - cmake_parse_arguments(PKG "${options}" "${oneValueArgs}" - "${multiValueArgs}" ${ARGN} ) - - rapids_cpm_find(glog ${PKG_VERSION} - GLOBAL_TARGETS glog::glog - BUILD_EXPORT_SET cuvs-exports - INSTALL_EXPORT_SET cuvs-exports - CPM_ARGS - GIT_REPOSITORY https://github.com/${PKG_FORK}/glog.git - GIT_TAG ${PKG_PINNED_TAG} - EXCLUDE_FROM_ALL ${PKG_EXCLUDE_FROM_ALL} - ) - - if(glog_ADDED) - message(VERBOSE "RAFT: Using glog located in ${glog_SOURCE_DIR}") - else() - message(VERBOSE "RAFT: Using glog located in ${glog_DIR}") - endif() - - -endfunction() - -# Change pinned tag here to test a commit in CI -# To use a different RAFT locally, set the CMake variable -# CPM_glog_SOURCE=/path/to/local/glog -find_and_configure_glog(VERSION 0.6.0 - FORK google - PINNED_TAG v0.6.0 - EXCLUDE_FROM_ALL ON - ) diff --git a/cpp/cmake/thirdparty/get_rmm.cmake b/cpp/cmake/thirdparty/get_rmm.cmake deleted file mode 100644 index 97796215e3..0000000000 --- a/cpp/cmake/thirdparty/get_rmm.cmake +++ /dev/null @@ -1,23 +0,0 @@ -#============================================================================= -# Copyright (c) 2021-2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#============================================================================= - -function(find_and_configure_rmm) - include(${rapids-cmake-dir}/cpm/rmm.cmake) - rapids_cpm_rmm(BUILD_EXPORT_SET cuvs-exports - INSTALL_EXPORT_SET cuvs-exports) -endfunction() - -find_and_configure_rmm() diff --git a/cpp/cmake/thirdparty/get_spdlog.cmake b/cpp/cmake/thirdparty/get_spdlog.cmake deleted file mode 100644 index 7be7804c7e..0000000000 --- a/cpp/cmake/thirdparty/get_spdlog.cmake +++ /dev/null @@ -1,33 +0,0 @@ -# ============================================================================= -# Copyright (c) 2021-2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -# in compliance with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License -# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing permissions and limitations under -# the License. -# ============================================================================= - -# Use CPM to find or clone speedlog -function(find_and_configure_spdlog) - - include(${rapids-cmake-dir}/cpm/spdlog.cmake) - rapids_cpm_spdlog(FMT_OPTION "EXTERNAL_FMT_HO" INSTALL_EXPORT_SET rmm-exports) - rapids_export_package(BUILD spdlog rmm-exports) - - if(spdlog_ADDED) - rapids_export( - BUILD spdlog - EXPORT_SET spdlog - GLOBAL_TARGETS spdlog spdlog_header_only - NAMESPACE spdlog::) - include("${rapids-cmake-dir}/export/find_package_root.cmake") - rapids_export_find_package_root(BUILD spdlog [=[${CMAKE_CURRENT_LIST_DIR}]=] EXPORT_SET rmm-exports) - endif() -endfunction() - -find_and_configure_spdlog() \ No newline at end of file diff --git a/cpp/cmake/thirdparty/get_thrust.cmake b/cpp/cmake/thirdparty/get_thrust.cmake deleted file mode 100644 index d2d6cb3e6a..0000000000 --- a/cpp/cmake/thirdparty/get_thrust.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# ============================================================================= -# Copyright (c) 2022-2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -# in compliance with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License -# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing permissions and limitations under -# the License. -# ============================================================================= - -# Use CPM to find or clone thrust -function(find_and_configure_thrust) - include(${rapids-cmake-dir}/cpm/thrust.cmake) - - rapids_cpm_thrust( NAMESPACE cuvs - BUILD_EXPORT_SET cuvs-exports - INSTALL_EXPORT_SET cuvs-exports) -endfunction() - -find_and_configure_thrust() From 6981a08ec2e81a37275a45bb6806294cb20bc5b3 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 19 Mar 2024 13:38:01 -0700 Subject: [PATCH 019/623] Add ability to allocate with RMM to the c-api and rust api (#56) Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Divye Gala (https://github.com/divyegala) - Corey J. Nolet (https://github.com/cjnolet) - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cuvs/pull/56 --- ci/build_rust.sh | 1 - cpp/include/cuvs/core/c_api.h | 44 ++++++++++++++++++++++++++++ cpp/src/core/c_api.cpp | 35 ++++++++++++++++++++++ rust/cuvs-sys/build.rs | 5 ++-- rust/cuvs/src/cagra/index.rs | 2 +- rust/cuvs/src/cagra/index_params.rs | 2 +- rust/cuvs/src/cagra/search_params.rs | 2 +- rust/cuvs/src/dlpack.rs | 44 +++++++++++++++------------- rust/cuvs/src/resources.rs | 19 ++++++++++++ 9 files changed, 126 insertions(+), 28 deletions(-) diff --git a/ci/build_rust.sh b/ci/build_rust.sh index 59f0924c80..ad2202734a 100755 --- a/ci/build_rust.sh +++ b/ci/build_rust.sh @@ -35,5 +35,4 @@ rapids-mamba-retry install \ libcuvs \ libraft -export EXTRA_CMAKE_ARGS="" bash ./build.sh rust diff --git a/cpp/include/cuvs/core/c_api.h b/cpp/include/cuvs/core/c_api.h index a15d7cd5c7..d931d6c13d 100644 --- a/cpp/include/cuvs/core/c_api.h +++ b/cpp/include/cuvs/core/c_api.h @@ -83,6 +83,50 @@ cuvsError_t cuvsResourcesDestroy(cuvsResources_t res); */ cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream); +/** + * @brief Get the cudaStream_t from a cuvsResources_t t + * + * @param[in] res cuvsResources_t opaque C handle + * @param[out] stream cudaStream_t stream to queue CUDA kernels + * @return cuvsError_t + */ +cuvsError_t cuvsStreamGet(cuvsResources_t res, cudaStream_t* stream); + +/** + * @brief Syncs the current CUDA stream on the resources object + * + * @param[in] res cuvsResources_t opaque C handle + * @return cuvsError_t + */ +cuvsError_t cuvsStreamSync(cuvsResources_t res); +/** @} */ + +/** + * @defgroup memory_c cuVS Memory Allocation + * @{ + */ + +/** + * @brief Allocates device memory using RMM + * + * + * @param[in] res cuvsResources_t opaque C handle + * @param[out] ptr Pointer to allocated device memory + * @param[in] bytes Size in bytes to allocate + * @return cuvsError_t + */ +cuvsError_t cuvsRMMAlloc(cuvsResources_t res, void** ptr, size_t bytes); + +/** + * @brief Deallocates device memory using RMM + * + * @param[in] res cuvsResources_t opaque C handle + * @param[in] ptr Pointer to allocated device memory to free + * @param[in] bytes Size in bytes to allocate + * @return cuvsError_t + */ +cuvsError_t cuvsRMMFree(cuvsResources_t res, void* ptr, size_t bytes); + /** @} */ #ifdef __cplusplus diff --git a/cpp/src/core/c_api.cpp b/cpp/src/core/c_api.cpp index 7ddb4f3e4c..96504a2fee 100644 --- a/cpp/src/core/c_api.cpp +++ b/cpp/src/core/c_api.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include extern "C" cuvsError_t cuvsResourcesCreate(cuvsResources_t* res) @@ -47,6 +48,40 @@ extern "C" cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream) }); } +extern "C" cuvsError_t cuvsStreamGet(cuvsResources_t res, cudaStream_t* stream) +{ + return cuvs::core::translate_exceptions([=] { + auto res_ptr = reinterpret_cast(res); + *stream = raft::resource::get_cuda_stream(*res_ptr); + }); +} + +extern "C" cuvsError_t cuvsStreamSync(cuvsResources_t res) +{ + return cuvs::core::translate_exceptions([=] { + auto res_ptr = reinterpret_cast(res); + raft::resource::sync_stream(*res_ptr); + }); +} + +extern "C" cuvsError_t cuvsRMMAlloc(cuvsResources_t res, void** ptr, size_t bytes) +{ + return cuvs::core::translate_exceptions([=] { + auto res_ptr = reinterpret_cast(res); + auto mr = rmm::mr::get_current_device_resource(); + *ptr = mr->allocate(bytes, raft::resource::get_cuda_stream(*res_ptr)); + }); +} + +extern "C" cuvsError_t cuvsRMMFree(cuvsResources_t res, void* ptr, size_t bytes) +{ + return cuvs::core::translate_exceptions([=] { + auto res_ptr = reinterpret_cast(res); + auto mr = rmm::mr::get_current_device_resource(); + mr->deallocate(ptr, bytes, raft::resource::get_cuda_stream(*res_ptr)); + }); +} + thread_local std::string last_error_text = ""; extern "C" const char* cuvsGetLastErrorText() diff --git a/rust/cuvs-sys/build.rs b/rust/cuvs-sys/build.rs index 816a6f2592..ec96725695 100644 --- a/rust/cuvs-sys/build.rs +++ b/rust/cuvs-sys/build.rs @@ -101,9 +101,8 @@ fn main() { .allowlist_type("(cuvs|cagra|DL).*") .allowlist_function("(cuvs|cagra).*") .rustified_enum("(cuvs|cagra|DL).*") - // also need some basic cuda mem functions - // (TODO: should we be adding in RMM support instead here?) - .allowlist_function("(cudaMalloc|cudaFree|cudaMemcpy)") + // also need some basic cuda mem functions for copying data + .allowlist_function("(cudaMemcpyAsync|cudaMemcpy)") .rustified_enum("cudaError") .generate() .expect("Unable to generate cagra_c bindings") diff --git a/rust/cuvs/src/cagra/index.rs b/rust/cuvs/src/cagra/index.rs index 3394889aad..6a5149f073 100644 --- a/rust/cuvs/src/cagra/index.rs +++ b/rust/cuvs/src/cagra/index.rs @@ -54,7 +54,7 @@ impl Index { /// Creates a new empty index pub fn new() -> Result { unsafe { - let mut index = core::mem::MaybeUninit::::uninit(); + let mut index = std::mem::MaybeUninit::::uninit(); check_cuvs(ffi::cuvsCagraIndexCreate(index.as_mut_ptr()))?; Ok(Index(index.assume_init())) } diff --git a/rust/cuvs/src/cagra/index_params.rs b/rust/cuvs/src/cagra/index_params.rs index ecc6605313..2e3367e06e 100644 --- a/rust/cuvs/src/cagra/index_params.rs +++ b/rust/cuvs/src/cagra/index_params.rs @@ -27,7 +27,7 @@ impl IndexParams { /// Returns a new IndexParams pub fn new() -> Result { unsafe { - let mut params = core::mem::MaybeUninit::::uninit(); + let mut params = std::mem::MaybeUninit::::uninit(); check_cuvs(ffi::cuvsCagraIndexParamsCreate(params.as_mut_ptr()))?; Ok(IndexParams(params.assume_init())) } diff --git a/rust/cuvs/src/cagra/search_params.rs b/rust/cuvs/src/cagra/search_params.rs index 14956966ea..7b891b0028 100644 --- a/rust/cuvs/src/cagra/search_params.rs +++ b/rust/cuvs/src/cagra/search_params.rs @@ -28,7 +28,7 @@ impl SearchParams { /// Returns a new SearchParams object pub fn new() -> Result { unsafe { - let mut params = core::mem::MaybeUninit::::uninit(); + let mut params = std::mem::MaybeUninit::::uninit(); check_cuvs(ffi::cuvsCagraSearchParamsCreate(params.as_mut_ptr()))?; Ok(SearchParams(params.assume_init())) } diff --git a/rust/cuvs/src/dlpack.rs b/rust/cuvs/src/dlpack.rs index a1d4e41c6d..238caec9d7 100644 --- a/rust/cuvs/src/dlpack.rs +++ b/rust/cuvs/src/dlpack.rs @@ -16,7 +16,7 @@ use std::convert::From; -use crate::error::{check_cuda, Result}; +use crate::error::{check_cuda, check_cuvs, Result}; use crate::resources::Resources; /// ManagedTensor is a wrapper around a dlpack DLManagedTensor object. @@ -33,36 +33,27 @@ impl ManagedTensor { &self.0 as *const _ as *mut _ } - fn bytes(&self) -> usize { - // figure out how many bytes to allocate - let mut bytes: usize = 1; - for x in 0..self.0.dl_tensor.ndim { - bytes *= unsafe { (*self.0.dl_tensor.shape.add(x as usize)) as usize }; - } - bytes *= (self.0.dl_tensor.dtype.bits / 8) as usize; - bytes - } - /// Creates a new ManagedTensor on the current GPU device, and copies /// the data into it. - pub fn to_device(&self, _res: &Resources) -> Result { + pub fn to_device(&self, res: &Resources) -> Result { unsafe { - let bytes = self.bytes(); + let bytes = dl_tensor_bytes(&self.0.dl_tensor); let mut device_data: *mut std::ffi::c_void = std::ptr::null_mut(); // allocate storage, copy over - check_cuda(ffi::cudaMalloc(&mut device_data as *mut _, bytes))?; - check_cuda(ffi::cudaMemcpy( + check_cuvs(ffi::cuvsRMMAlloc(res.0, &mut device_data as *mut _, bytes))?; + + check_cuda(ffi::cudaMemcpyAsync( device_data, self.0.dl_tensor.data, bytes, ffi::cudaMemcpyKind_cudaMemcpyDefault, + res.get_cuda_stream()?, ))?; let mut ret = self.0.clone(); ret.dl_tensor.data = device_data; - // call cudaFree automatically to clean up data - ret.deleter = Some(cuda_free_tensor); + ret.deleter = Some(rmm_free_tensor); ret.dl_tensor.device.device_type = ffi::DLDeviceType::kDLCUDA; Ok(ManagedTensor(ret)) @@ -80,21 +71,32 @@ impl ManagedTensor { arr: &mut ndarray::ArrayBase, ) -> Result<()> { unsafe { - let bytes = self.bytes(); + let bytes = dl_tensor_bytes(&self.0.dl_tensor); check_cuda(ffi::cudaMemcpy( arr.as_mut_ptr() as *mut std::ffi::c_void, self.0.dl_tensor.data, bytes, ffi::cudaMemcpyKind_cudaMemcpyDefault, ))?; - Ok(()) } } } -unsafe extern "C" fn cuda_free_tensor(self_: *mut ffi::DLManagedTensor) { - let _ = ffi::cudaFree((*self_).dl_tensor.data); +/// Figures out how many bytes are in a DLTensor +fn dl_tensor_bytes(tensor: &ffi::DLTensor) -> usize { + let mut bytes: usize = 1; + for dim in 0..tensor.ndim { + bytes *= unsafe { (*tensor.shape.add(dim as usize)) as usize }; + } + bytes *= (tensor.dtype.bits / 8) as usize; + bytes +} + +unsafe extern "C" fn rmm_free_tensor(self_: *mut ffi::DLManagedTensor) { + let bytes = dl_tensor_bytes(&(*self_).dl_tensor); + let res = Resources::new().unwrap(); + let _ = ffi::cuvsRMMFree(res.0, (*self_).dl_tensor.data as *mut _, bytes); } /// Create a non-owning view of a Tensor from a ndarray diff --git a/rust/cuvs/src/resources.rs b/rust/cuvs/src/resources.rs index a5c503dc53..0c60cf6697 100644 --- a/rust/cuvs/src/resources.rs +++ b/rust/cuvs/src/resources.rs @@ -32,6 +32,25 @@ impl Resources { } Ok(Resources(res)) } + + /// Sets the current cuda stream + pub fn set_cuda_stream(&self, stream: ffi::cudaStream_t) -> Result<()> { + unsafe { check_cuvs(ffi::cuvsStreamSet(self.0, stream)) } + } + + /// Gets the current cuda stream + pub fn get_cuda_stream(&self) -> Result { + unsafe { + let mut stream = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsStreamGet(self.0, stream.as_mut_ptr()))?; + Ok(stream.assume_init()) + } + } + + /// Syncs the current cuda stream + pub fn sync_stream(&self) -> Result<()> { + unsafe { check_cuvs(ffi::cuvsStreamSync(self.0)) } + } } impl Drop for Resources { From 18bab30e83cb793837224ce88478458f5571ea18 Mon Sep 17 00:00:00 2001 From: Victor Lafargue Date: Wed, 20 Mar 2024 22:56:23 +0100 Subject: [PATCH 020/623] KNN bruteforce and IVF methods C/C++ API (#33) Authors: - Victor Lafargue (https://github.com/viclafargue) - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Ben Frederickson (https://github.com/benfred) - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/33 --- .gitignore | 6 + cpp/CMakeLists.txt | 35 +- cpp/include/cuvs/core/detail/interop.hpp | 6 +- cpp/include/cuvs/core/interop.hpp | 6 +- cpp/include/cuvs/distance/distance_types.h | 70 ++ cpp/include/cuvs/neighbors/brute_force.h | 163 +++ cpp/include/cuvs/neighbors/brute_force.hpp | 146 +++ cpp/include/cuvs/neighbors/cagra.h | 2 +- cpp/include/cuvs/neighbors/ivf_flat.h | 283 +++++ cpp/include/cuvs/neighbors/ivf_flat.hpp | 1026 +++++++++++++++++ cpp/include/cuvs/neighbors/ivf_pq.h | 356 ++++++ cpp/include/cuvs/neighbors/ivf_pq.hpp | 868 ++++++++++++++ cpp/src/neighbors/brute_force.cu | 55 + cpp/src/neighbors/brute_force_c.cpp | 167 +++ cpp/src/neighbors/brute_force_index.cu | 86 ++ cpp/src/neighbors/cagra_c.cpp | 6 +- .../neighbors/ivf_flat/generate_ivf_flat.py | 174 +++ .../ivf_flat/ivf_flat_build_float_int64_t.cpp | 52 + .../ivf_flat_build_int8_t_int64_t.cpp | 52 + .../ivf_flat_build_uint8_t_int64_t.cpp | 52 + .../ivf_flat_extend_float_int64_t.cpp | 55 + .../ivf_flat_extend_int8_t_int64_t.cpp | 55 + .../ivf_flat_extend_uint8_t_int64_t.cpp | 55 + .../ivf_flat_search_float_int64_t.cpp | 46 + .../ivf_flat_search_int8_t_int64_t.cpp | 46 + .../ivf_flat_search_uint8_t_int64_t.cpp | 46 + .../ivf_flat_serialize_float_int64_t.cpp | 64 + .../ivf_flat_serialize_int8_t_int64_t.cpp | 64 + .../ivf_flat_serialize_uint8_t_int64_t.cpp | 64 + cpp/src/neighbors/ivf_flat_c.cpp | 246 ++++ cpp/src/neighbors/ivf_flat_index.cpp | 163 +++ cpp/src/neighbors/ivf_pq/generate_ivf_pq.py | 138 +++ .../ivf_pq/ivf_pq_build_float_int64_t.cpp | 52 + .../ivf_pq/ivf_pq_build_int8_t_int64_t.cpp | 52 + .../ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp | 52 + .../ivf_pq/ivf_pq_extend_float_int64_t.cpp | 55 + .../ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp | 55 + .../ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp | 55 + .../ivf_pq/ivf_pq_search_float_int64_t.cpp | 46 + .../ivf_pq/ivf_pq_search_int8_t_int64_t.cpp | 46 + .../ivf_pq/ivf_pq_search_uint8_t_int64_t.cpp | 46 + cpp/src/neighbors/ivf_pq_c.cpp | 235 ++++ cpp/src/neighbors/ivf_pq_index.cpp | 237 ++++ cpp/src/neighbors/ivf_pq_serialize.cpp | 40 + cpp/test/CMakeLists.txt | 54 + cpp/test/neighbors/ann_ivf_flat.cuh | 643 +++++++++++ .../ann_ivf_flat/test_float_int64_t.cu | 28 + .../ann_ivf_flat/test_int8_t_int64_t.cu | 28 + .../ann_ivf_flat/test_uint8_t_int64_t.cu | 28 + cpp/test/neighbors/ann_ivf_flat_c.cu | 147 +++ cpp/test/neighbors/ann_ivf_pq.cuh | 981 ++++++++++++++++ .../ann_ivf_pq/test_float_int64_t.cu | 27 + .../ann_ivf_pq/test_int8_t_int64_t.cu | 27 + .../ann_ivf_pq/test_uint8_t_int64_t.cu | 27 + cpp/test/neighbors/ann_ivf_pq_c.cu | 147 +++ cpp/test/neighbors/brute_force.cu | 182 +++ cpp/test/neighbors/brute_force_c.cu | 137 +++ cpp/test/neighbors/ivf_pq_helpers.cuh | 428 +++++++ cpp/test/neighbors/run_brute_force_c.c | 94 ++ cpp/test/neighbors/run_ivf_flat_c.c | 105 ++ cpp/test/neighbors/run_ivf_pq_c.c | 105 ++ docs/source/c_api/neighbors.rst | 7 +- docs/source/c_api/neighbors_bruteforce_c.rst | 34 + docs/source/c_api/neighbors_ivf_flat_c.rst | 50 + docs/source/c_api/neighbors_ivf_pq_c.rst | 50 + docs/source/cpp_api.rst | 1 + docs/source/cpp_api/distance.rst | 19 + docs/source/cpp_api/neighbors.rst | 5 +- docs/source/cpp_api/neighbors_bruteforce.rst | 36 + docs/source/cpp_api/neighbors_ivf_flat.rst | 68 ++ docs/source/cpp_api/neighbors_ivf_pq.rst | 68 ++ 71 files changed, 9135 insertions(+), 15 deletions(-) create mode 100644 cpp/include/cuvs/distance/distance_types.h create mode 100644 cpp/include/cuvs/neighbors/brute_force.h create mode 100644 cpp/include/cuvs/neighbors/brute_force.hpp create mode 100644 cpp/include/cuvs/neighbors/ivf_flat.h create mode 100644 cpp/include/cuvs/neighbors/ivf_flat.hpp create mode 100644 cpp/include/cuvs/neighbors/ivf_pq.h create mode 100644 cpp/include/cuvs/neighbors/ivf_pq.hpp create mode 100644 cpp/src/neighbors/brute_force.cu create mode 100644 cpp/src/neighbors/brute_force_c.cpp create mode 100644 cpp/src/neighbors/brute_force_index.cu create mode 100644 cpp/src/neighbors/ivf_flat/generate_ivf_flat.py create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_build_float_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_build_int8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_build_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_extend_float_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_extend_int8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_extend_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_search_float_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_search_int8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_search_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_serialize_float_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_serialize_int8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat/ivf_flat_serialize_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_flat_c.cpp create mode 100644 cpp/src/neighbors/ivf_flat_index.cpp create mode 100644 cpp/src/neighbors/ivf_pq/generate_ivf_pq.py create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build_float_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build_int8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_extend_float_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_search_float_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_search_int8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_search_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq_c.cpp create mode 100644 cpp/src/neighbors/ivf_pq_index.cpp create mode 100644 cpp/src/neighbors/ivf_pq_serialize.cpp create mode 100644 cpp/test/neighbors/ann_ivf_flat.cuh create mode 100644 cpp/test/neighbors/ann_ivf_flat/test_float_int64_t.cu create mode 100644 cpp/test/neighbors/ann_ivf_flat/test_int8_t_int64_t.cu create mode 100644 cpp/test/neighbors/ann_ivf_flat/test_uint8_t_int64_t.cu create mode 100644 cpp/test/neighbors/ann_ivf_flat_c.cu create mode 100644 cpp/test/neighbors/ann_ivf_pq.cuh create mode 100644 cpp/test/neighbors/ann_ivf_pq/test_float_int64_t.cu create mode 100644 cpp/test/neighbors/ann_ivf_pq/test_int8_t_int64_t.cu create mode 100644 cpp/test/neighbors/ann_ivf_pq/test_uint8_t_int64_t.cu create mode 100644 cpp/test/neighbors/ann_ivf_pq_c.cu create mode 100644 cpp/test/neighbors/brute_force.cu create mode 100644 cpp/test/neighbors/brute_force_c.cu create mode 100644 cpp/test/neighbors/ivf_pq_helpers.cuh create mode 100644 cpp/test/neighbors/run_brute_force_c.c create mode 100644 cpp/test/neighbors/run_ivf_flat_c.c create mode 100644 cpp/test/neighbors/run_ivf_pq_c.c create mode 100644 docs/source/c_api/neighbors_bruteforce_c.rst create mode 100644 docs/source/c_api/neighbors_ivf_flat_c.rst create mode 100644 docs/source/c_api/neighbors_ivf_pq_c.rst create mode 100644 docs/source/cpp_api/distance.rst create mode 100644 docs/source/cpp_api/neighbors_bruteforce.rst create mode 100644 docs/source/cpp_api/neighbors_ivf_flat.rst create mode 100644 docs/source/cpp_api/neighbors_ivf_pq.rst diff --git a/.gitignore b/.gitignore index 9fcde3fb3b..68996dbdf0 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,9 @@ docs/source/_static/rust # clang tooling compile_commands.json .clangd/ + +# serialized ann indexes +cagra_index +ivf_flat_index +ivf_pq_index + diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index f6d6a2223d..c2d311bf23 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -187,6 +187,8 @@ include(cmake/thirdparty/get_cutlass.cmake) add_library( cuvs SHARED + src/neighbors/brute_force_index.cu + src/neighbors/brute_force.cu src/neighbors/cagra_build_float.cpp src/neighbors/cagra_build_int8.cpp src/neighbors/cagra_build_uint8.cpp @@ -197,6 +199,30 @@ add_library( src/neighbors/cagra_serialize_float.cpp src/neighbors/cagra_serialize_int8.cpp src/neighbors/cagra_serialize_uint8.cpp + src/neighbors/ivf_flat_index.cpp + src/neighbors/ivf_flat/ivf_flat_build_float_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_build_int8_t_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_build_uint8_t_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_extend_float_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_extend_int8_t_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_extend_uint8_t_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_search_float_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_search_int8_t_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_search_uint8_t_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_serialize_float_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_serialize_int8_t_int64_t.cpp + src/neighbors/ivf_flat/ivf_flat_serialize_uint8_t_int64_t.cpp + src/neighbors/ivf_pq_index.cpp + src/neighbors/ivf_pq/ivf_pq_build_float_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_build_int8_t_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_extend_float_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_search_float_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_search_int8_t_int64_t.cpp + src/neighbors/ivf_pq/ivf_pq_search_uint8_t_int64_t.cpp + src/neighbors/ivf_pq_serialize.cpp ) target_compile_options( @@ -297,7 +323,14 @@ target_link_options(cuvs PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld") # ################################################################################################## # * cuvs_c ------------------------------------------------------------------------------- if(BUILD_C_LIBRARY) - add_library(cuvs_c SHARED src/core/c_api.cpp src/neighbors/cagra_c.cpp) + add_library( + cuvs_c SHARED + src/core/c_api.cpp + src/neighbors/brute_force_c.cpp + src/neighbors/ivf_flat_c.cpp + src/neighbors/ivf_pq_c.cpp + src/neighbors/cagra_c.cpp + ) add_library(cuvs::c_api ALIAS cuvs_c) diff --git a/cpp/include/cuvs/core/detail/interop.hpp b/cpp/include/cuvs/core/detail/interop.hpp index f218dc554d..208daaae7c 100644 --- a/cpp/include/cuvs/core/detail/interop.hpp +++ b/cpp/include/cuvs/core/detail/interop.hpp @@ -53,20 +53,20 @@ DLDataType data_type_to_DLDataType() } } -bool is_dlpack_device_compatible(DLTensor tensor) +inline bool is_dlpack_device_compatible(DLTensor tensor) { return tensor.device.device_type == kDLCUDAManaged || tensor.device.device_type == kDLCUDAHost || tensor.device.device_type == kDLCUDA; } -bool is_dlpack_host_compatible(DLTensor tensor) +inline bool is_dlpack_host_compatible(DLTensor tensor) { return tensor.device.device_type == kDLCUDAManaged || tensor.device.device_type == kDLCUDAHost || tensor.device.device_type == kDLCPU; } template > -MdspanType from_dlpack(DLManagedTensor* managed_tensor) +inline MdspanType from_dlpack(DLManagedTensor* managed_tensor) { auto tensor = managed_tensor->dl_tensor; diff --git a/cpp/include/cuvs/core/interop.hpp b/cpp/include/cuvs/core/interop.hpp index 9510022db8..2462f02ec0 100644 --- a/cpp/include/cuvs/core/interop.hpp +++ b/cpp/include/cuvs/core/interop.hpp @@ -33,7 +33,7 @@ namespace cuvs::core { * @param[in] tensor DLTensor object to check underlying memory type * @return bool */ -bool is_dlpack_device_compatible(DLTensor tensor) +inline bool is_dlpack_device_compatible(DLTensor tensor) { return detail::is_dlpack_device_compatible(tensor); } @@ -46,7 +46,7 @@ bool is_dlpack_device_compatible(DLTensor tensor) * @param tensor DLTensor object to check underlying memory type * @return bool */ -bool is_dlpack_host_compatible(DLTensor tensor) +inline bool is_dlpack_host_compatible(DLTensor tensor) { return detail::is_dlpack_host_compatible(tensor); } @@ -72,7 +72,7 @@ bool is_dlpack_host_compatible(DLTensor tensor) * @return MdspanType */ template > -MdspanType from_dlpack(DLManagedTensor* managed_tensor) +inline MdspanType from_dlpack(DLManagedTensor* managed_tensor) { return detail::from_dlpack(managed_tensor); } diff --git a/cpp/include/cuvs/distance/distance_types.h b/cpp/include/cuvs/distance/distance_types.h new file mode 100644 index 0000000000..8e9a4149cb --- /dev/null +++ b/cpp/include/cuvs/distance/distance_types.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** enum to tell how to compute distance */ +enum DistanceType { + + /** evaluate as dist_ij = sum(x_ik^2) + sum(y_ij)^2 - 2*sum(x_ik * y_jk) */ + L2Expanded = 0, + /** same as above, but inside the epilogue, perform square root operation */ + L2SqrtExpanded = 1, + /** cosine distance */ + CosineExpanded = 2, + /** L1 distance */ + L1 = 3, + /** evaluate as dist_ij += (x_ik - y-jk)^2 */ + L2Unexpanded = 4, + /** same as above, but inside the epilogue, perform square root operation */ + L2SqrtUnexpanded = 5, + /** basic inner product **/ + InnerProduct = 6, + /** Chebyshev (Linf) distance **/ + Linf = 7, + /** Canberra distance **/ + Canberra = 8, + /** Generalized Minkowski distance **/ + LpUnexpanded = 9, + /** Correlation distance **/ + CorrelationExpanded = 10, + /** Jaccard distance **/ + JaccardExpanded = 11, + /** Hellinger distance **/ + HellingerExpanded = 12, + /** Haversine distance **/ + Haversine = 13, + /** Bray-Curtis distance **/ + BrayCurtis = 14, + /** Jensen-Shannon distance**/ + JensenShannon = 15, + /** Hamming distance **/ + HammingUnexpanded = 16, + /** KLDivergence **/ + KLDivergence = 17, + /** RusselRao **/ + RusselRaoExpanded = 18, + /** Dice-Sorensen distance **/ + DiceExpanded = 19, + /** Precomputed (special value) **/ + Precomputed = 100 +}; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/cpp/include/cuvs/neighbors/brute_force.h b/cpp/include/cuvs/neighbors/brute_force.h new file mode 100644 index 0000000000..0bb4d6bdbf --- /dev/null +++ b/cpp/include/cuvs/neighbors/brute_force.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup bruteforce_c_index Bruteforce index + * @{ + */ +/** + * @brief Struct to hold address of cuvs::neighbors::brute_force::index and its active trained dtype + * + */ +typedef struct { + uintptr_t addr; + DLDataType dtype; +} bruteForceIndex; + +typedef bruteForceIndex* cuvsBruteForceIndex_t; + +/** + * @brief Allocate BRUTEFORCE index + * + * @param[in] index cuvsBruteForceIndex_t to allocate + * @return cuvsError_t + */ +cuvsError_t bruteForceIndexCreate(cuvsBruteForceIndex_t* index); + +/** + * @brief De-allocate BRUTEFORCE index + * + * @param[in] index cuvsBruteForceIndex_t to de-allocate + */ +cuvsError_t bruteForceIndexDestroy(cuvsBruteForceIndex_t index); +/** + * @} + */ + +/** + * @defgroup bruteforce_c_index_build Bruteforce index build + * @{ + */ +/** + * @brief Build a BRUTEFORCE index with a `DLManagedTensor` which has underlying + * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`, + * or `kDLCPU`. Also, acceptable underlying types are: + * 1. `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * 2. `kDLDataType.code == kDLInt` and `kDLDataType.bits = 8` + * 3. `kDLDataType.code == kDLUInt` and `kDLDataType.bits = 8` + * + * @code {.c} + * #include + * #include + * + * // Create cuvsResources_t + * cuvsResources_t res; + * cuvsError_t res_create_status = cuvsResourcesCreate(&res); + * + * // Assume a populated `DLManagedTensor` type here + * DLManagedTensor dataset; + * + * // Create BRUTEFORCE index + * cuvsBruteForceIndex_t index; + * cuvsError_t index_create_status = bruteForceIndexCreate(&index); + * + * // Build the BRUTEFORCE Index + * cuvsError_t build_status = bruteForceBuild(res, &dataset_tensor, L2Expanded, 0.f, index); + * + * // de-allocate `index` and `res` + * cuvsError_t index_destroy_status = bruteForceIndexDestroy(index); + * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); + * @endcode + * + * @param[in] res cuvsResources_t opaque C handle + * @param[in] dataset DLManagedTensor* training dataset + * @param[in] metric metric + * @param[in] metric_arg metric_arg + * @param[out] index cuvsBruteForceIndex_t Newly built BRUTEFORCE index + * @return cuvsError_t + */ +cuvsError_t bruteForceBuild(cuvsResources_t res, + DLManagedTensor* dataset, + enum DistanceType metric, + float metric_arg, + cuvsBruteForceIndex_t index); +/** + * @} + */ + +/** + * @defgroup bruteforce_c_index_search Bruteforce index search + * @{ + */ +/** + * @brief Search a BRUTEFORCE index with a `DLManagedTensor` which has underlying + * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`. + * It is also important to note that the BRUTEFORCE index must have been built + * with the same type of `queries`, such that `index.dtype.code == + * queries.dl_tensor.dtype.code` Types for input are: + * 1. `queries`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * 2. `neighbors`: `kDLDataType.code == kDLUInt` and `kDLDataType.bits = 32` + * 3. `distances`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * + * @code {.c} + * #include + * #include + * + * // Create cuvsResources_t + * cuvsResources_t res; + * cuvsError_t res_create_status = cuvsResourcesCreate(&res); + * + * // Assume a populated `DLManagedTensor` type here + * DLManagedTensor dataset; + * DLManagedTensor queries; + * DLManagedTensor neighbors; + * + * // Search the `index` built using `bruteForceBuild` + * cuvsError_t search_status = bruteForceSearch(res, index, &queries, &neighbors, &distances); + * + * // de-allocate `res` + * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); + * @endcode + * + * @param[in] res cuvsResources_t opaque C handle + * @param[in] index bruteForceIndex which has been returned by `bruteForceBuild` + * @param[in] queries DLManagedTensor* queries dataset to search + * @param[out] neighbors DLManagedTensor* output `k` neighbors for queries + * @param[out] distances DLManagedTensor* output `k` distances for queries + */ +cuvsError_t bruteForceSearch(cuvsResources_t res, + cuvsBruteForceIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances); +/** + * @} + */ + +#ifdef __cplusplus +} +#endif diff --git a/cpp/include/cuvs/neighbors/brute_force.hpp b/cpp/include/cuvs/neighbors/brute_force.hpp new file mode 100644 index 0000000000..26951e1ec3 --- /dev/null +++ b/cpp/include/cuvs/neighbors/brute_force.hpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "ann_types.hpp" +#include +#include +#include + +namespace cuvs::neighbors::brute_force { + +/** + * @defgroup bruteforce_cpp_index Bruteforce index + * @{ + */ +/** + * @brief Brute Force index. + * + * The index stores the dataset and norms for the dataset in device memory. + * + * @tparam T data element type + */ +template +struct index : cuvs::neighbors::ann::index { + public: + index(const index&) = delete; + index(index&&) = default; + index& operator=(const index&) = delete; + index& operator=(index&&) = default; + ~index() = default; + index(void* raft_index); + + /** Distance metric used for retrieval */ + cuvs::distance::DistanceType metric() const noexcept; + + /** Metric argument */ + T metric_arg() const noexcept; + + /** Total length of the index (number of vectors). */ + size_t size() const noexcept; + + /** Dimensionality of the data. */ + size_t dim() const noexcept; + + /** Dataset [size, dim] */ + raft::device_matrix_view dataset() const noexcept; + + /** Dataset norms */ + raft::device_vector_view norms() const; + + /** Whether ot not this index has dataset norms */ + bool has_norms() const noexcept; + + // Get pointer to underlying RAFT index, not meant to be used outside of cuVS + inline const void* get_raft_index() const noexcept { return raft_index_.get(); } + + private: + std::unique_ptr raft_index_; +}; +/** + * @} + */ + +/** + * @defgroup bruteforce_cpp_index_build Bruteforce index build + * @{ + */ +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // create and fill the index from a [N, D] dataset + * auto index = brute_force::build(handle, dataset, metric); + * @endcode + * + * @param[in] handle + * @param[in] dataset a device pointer to a row-major matrix [n_rows, dim] + * @param[in] metric cuvs::distance::DistanceType + * @param[in] metric_arg metric argument + * + * @return the constructed ivf-flat index + */ +auto build(raft::resources const& handle, + raft::device_matrix_view dataset, + cuvs::distance::DistanceType metric = cuvs::distance::DistanceType::L2Unexpanded, + float metric_arg = 0) -> cuvs::neighbors::brute_force::index; +/** + * @} + */ + +/** + * @defgroup bruteforce_cpp_index_search Bruteforce index search + * @{ + */ +/** + * @brief Search ANN using the constructed index. + * + * See the [ivf_flat::build](#ivf_flat::build) documentation for a usage example. + * + * Note, this function requires a temporary buffer to store intermediate results between cuda kernel + * calls, which may lead to undesirable allocations and slowdown. To alleviate the problem, you can + * pass a pool memory resource or a large enough pre-allocated memory resource to reduce or + * eliminate entirely allocations happening within `search`: + * @code{.cpp} + * ... + * // Use the same allocator across multiple searches to reduce the number of + * // cuda memory allocations + * brute_force::search(handle, index, queries1, out_inds1, out_dists1); + * brute_force::search(handle, index, queries2, out_inds2, out_dists2); + * brute_force::search(handle, index, queries3, out_inds3, out_dists3); + * ... + * @endcode + * + * @param[in] handle + * @param[in] index ivf-flat constructed index + * @param[in] queries a device pointer to a row-major matrix [n_queries, index->dim()] + * @param[out] neighbors a device pointer to the indices of the neighbors in the source dataset + * [n_queries, k] + * @param[out] distances a device pointer to the distances to the selected neighbors [n_queries, k] + */ +void search(raft::resources const& handle, + const cuvs::neighbors::brute_force::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); +/** + * @} + */ + +} // namespace cuvs::neighbors::brute_force diff --git a/cpp/include/cuvs/neighbors/cagra.h b/cpp/include/cuvs/neighbors/cagra.h index ae8f8ea01d..912430a47b 100644 --- a/cpp/include/cuvs/neighbors/cagra.h +++ b/cpp/include/cuvs/neighbors/cagra.h @@ -273,7 +273,7 @@ cuvsError_t cuvsCagraBuild(cuvsResources_t res, * It is also important to note that the CAGRA Index must have been built * with the same type of `queries`, such that `index.dtype.code == * queries.dl_tensor.dtype.code` Types for input are: - * 1. `queries`: kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * 1. `queries`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` * 2. `neighbors`: `kDLDataType.code == kDLUInt` and `kDLDataType.bits = 32` * 3. `distances`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` * diff --git a/cpp/include/cuvs/neighbors/ivf_flat.h b/cpp/include/cuvs/neighbors/ivf_flat.h new file mode 100644 index 0000000000..08200ae7da --- /dev/null +++ b/cpp/include/cuvs/neighbors/ivf_flat.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup ivf_flat_c_index_params IVF-Flat index build parameters + * @{ + */ +/** + * @brief Supplemental parameters to build IVF-Flat Index + * + */ +struct ivfFlatIndexParams { + /** Distance type. */ + enum DistanceType metric; + /** The argument used by some distance metrics. */ + float metric_arg; + /** + * Whether to add the dataset content to the index, i.e.: + * + * - `true` means the index is filled with the dataset vectors and ready to search after calling + * `build`. + * - `false` means `build` only trains the underlying model (e.g. quantizer or clustering), but + * the index is left empty; you'd need to call `extend` on the index afterwards to populate it. + */ + bool add_data_on_build; + /** The number of inverted lists (clusters) */ + uint32_t n_lists; + /** The number of iterations searching for kmeans centers (index building). */ + uint32_t kmeans_n_iters; + /** The fraction of data to use during iterative kmeans building. */ + double kmeans_trainset_fraction; + /** + * By default (adaptive_centers = false), the cluster centers are trained in `ivf_flat::build`, + * and never modified in `ivf_flat::extend`. As a result, you may need to retrain the index + * from scratch after invoking (`ivf_flat::extend`) a few times with new data, the distribution of + * which is no longer representative of the original training set. + * + * The alternative behavior (adaptive_centers = true) is to update the cluster centers for new + * data when it is added. In this case, `index.centers()` are always exactly the centroids of the + * data in the corresponding clusters. The drawback of this behavior is that the centroids depend + * on the order of adding new data (through the classification of the added data); that is, + * `index.centers()` "drift" together with the changing distribution of the newly added data. + */ + bool adaptive_centers; + /** + * By default, the algorithm allocates more space than necessary for individual clusters + * (`list_data`). This allows to amortize the cost of memory allocation and reduce the number of + * data copies during repeated calls to `extend` (extending the database). + * + * The alternative is the conservative allocation behavior; when enabled, the algorithm always + * allocates the minimum amount of memory required to store the given number of records. Set this + * flag to `true` if you prefer to use as little GPU memory for the database as possible. + */ + bool conservative_memory_allocation; +}; + +typedef struct ivfFlatIndexParams* cuvsIvfFlatIndexParams_t; + +/** + * @brief Allocate IVF-Flat Index params, and populate with default values + * + * @param[in] index_params cuvsIvfFlatIndexParams_t to allocate + * @return cuvsError_t + */ +cuvsError_t cuvsIvfFlatIndexParamsCreate(cuvsIvfFlatIndexParams_t* index_params); + +/** + * @brief De-allocate IVF-Flat Index params + * + * @param[in] index_params + * @return cuvsError_t + */ +cuvsError_t cuvsIvfFlatIndexParamsDestroy(cuvsIvfFlatIndexParams_t index_params); +/** + * @} + */ + +/** + * @defgroup ivf_flat_c_search_params IVF-Flat index search parameters + * @{ + */ +/** + * @brief Supplemental parameters to search IVF-Flat index + * + */ +struct ivfFlatSearchParams { + /** The number of clusters to search. */ + uint32_t n_probes; +}; + +typedef struct ivfFlatSearchParams* cuvsIvfFlatSearchParams_t; + +/** + * @brief Allocate IVF-Flat search params, and populate with default values + * + * @param[in] params cuvsIvfFlatSearchParams_t to allocate + * @return cuvsError_t + */ +cuvsError_t cuvsIvfFlatSearchParamsCreate(cuvsIvfFlatSearchParams_t* params); + +/** + * @brief De-allocate IVF-Flat search params + * + * @param[in] params + * @return cuvsError_t + */ +cuvsError_t cuvsIvfFlatSearchParamsDestroy(cuvsIvfFlatSearchParams_t params); +/** + * @} + */ + +/** + * @defgroup ivf_flat_c_index IVF-Flat index + * @{ + */ +/** + * @brief Struct to hold address of cuvs::neighbors::ivf_flat::index and its active trained dtype + * + */ +typedef struct { + uintptr_t addr; + DLDataType dtype; +} ivfFlatIndex; + +typedef ivfFlatIndex* cuvsIvfFlatIndex_t; + +/** + * @brief Allocate IVF-Flat index + * + * @param[in] index cuvsIvfFlatIndex_t to allocate + * @return ivfFlatError_t + */ +cuvsError_t ivfFlatIndexCreate(cuvsIvfFlatIndex_t* index); + +/** + * @brief De-allocate IVF-Flat index + * + * @param[in] index cuvsIvfFlatIndex_t to de-allocate + */ +cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index); +/** + * @} + */ + +/** + * @defgroup ivf_flat_c_index_build IVF-Flat index build + * @{ + */ +/** + * @brief Build a IVF-Flat index with a `DLManagedTensor` which has underlying + * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`, + * or `kDLCPU`. Also, acceptable underlying types are: + * 1. `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * 2. `kDLDataType.code == kDLInt` and `kDLDataType.bits = 8` + * 3. `kDLDataType.code == kDLUInt` and `kDLDataType.bits = 8` + * + * @code {.c} + * #include + * #include + * + * // Create cuvsResources_t + * cuvsResources_t res; + * cuvsError_t res_create_status = cuvsResourcesCreate(&res); + * + * // Assume a populated `DLManagedTensor` type here + * DLManagedTensor dataset; + * + * // Create default index params + * cuvsIvfFlatIndexParams_t index_params; + * cuvsError_t params_create_status = cuvsIvfFlatIndexParamsCreate(&index_params); + * + * // Create IVF-Flat index + * cuvsIvfFlatIndex_t index; + * cuvsError_t index_create_status = ivfFlatIndexCreate(&index); + * + * // Build the IVF-Flat Index + * cuvsError_t build_status = ivfFlatBuild(res, index_params, &dataset, index); + * + * // de-allocate `index_params`, `index` and `res` + * cuvsError_t params_destroy_status = cuvsIvfFlatIndexParamsDestroy(index_params); + * cuvsError_t index_destroy_status = ivfFlatIndexDestroy(index); + * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); + * @endcode + * + * @param[in] res cuvsResources_t opaque C handle + * @param[in] index_params cuvsIvfFlatIndexParams_t used to build IVF-Flat index + * @param[in] dataset DLManagedTensor* training dataset + * @param[out] index cuvsIvfFlatIndex_t Newly built IVF-Flat index + * @return cuvsError_t + */ +cuvsError_t ivfFlatBuild(cuvsResources_t res, + cuvsIvfFlatIndexParams_t index_params, + DLManagedTensor* dataset, + cuvsIvfFlatIndex_t index); +/** + * @} + */ + +/** + * @defgroup ivf_flat_c_index_search IVF-Flat index search + * @{ + */ +/** + * @brief Search a IVF-Flat index with a `DLManagedTensor` which has underlying + * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`. + * It is also important to note that the IVF-Flat Index must have been built + * with the same type of `queries`, such that `index.dtype.code == + * queries.dl_tensor.dtype.code` Types for input are: + * 1. `queries`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * 2. `neighbors`: `kDLDataType.code == kDLUInt` and `kDLDataType.bits = 32` + * 3. `distances`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * + * @code {.c} + * #include + * #include + * + * // Create cuvsResources_t + * cuvsResources_t res; + * cuvsError_t res_create_status = cuvsResourcesCreate(&res); + * + * // Assume a populated `DLManagedTensor` type here + * DLManagedTensor dataset; + * DLManagedTensor queries; + * DLManagedTensor neighbors; + * + * // Create default search params + * cuvsIvfFlatSearchParams_t search_params; + * cuvsError_t params_create_status = cuvsIvfFlatSearchParamsCreate(&search_params); + * + * // Search the `index` built using `ivfFlatBuild` + * cuvsError_t search_status = ivfFlatSearch(res, search_params, index, &queries, &neighbors, + * &distances); + * + * // de-allocate `search_params` and `res` + * cuvsError_t params_destroy_status = cuvsIvfFlatSearchParamsDestroy(search_params); + * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); + * @endcode + * + * @param[in] res cuvsResources_t opaque C handle + * @param[in] search_params cuvsIvfFlatSearchParams_t used to search IVF-Flat index + * @param[in] index ivfFlatIndex which has been returned by `ivfFlatBuild` + * @param[in] queries DLManagedTensor* queries dataset to search + * @param[out] neighbors DLManagedTensor* output `k` neighbors for queries + * @param[out] distances DLManagedTensor* output `k` distances for queries + */ +cuvsError_t ivfFlatSearch(cuvsResources_t res, + cuvsIvfFlatSearchParams_t search_params, + cuvsIvfFlatIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances); +/** + * @} + */ + +#ifdef __cplusplus +} +#endif diff --git a/cpp/include/cuvs/neighbors/ivf_flat.hpp b/cpp/include/cuvs/neighbors/ivf_flat.hpp new file mode 100644 index 0000000000..efb32e024d --- /dev/null +++ b/cpp/include/cuvs/neighbors/ivf_flat.hpp @@ -0,0 +1,1026 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "ann_types.hpp" +#include + +namespace cuvs::neighbors::ivf_flat { +/** + * @defgroup ivf_flat_cpp_index_params IVF-Flat index build parameters + * @{ + */ +struct index_params : ann::index_params { + /** The number of inverted lists (clusters) */ + uint32_t n_lists = 1024; + /** The number of iterations searching for kmeans centers (index building). */ + uint32_t kmeans_n_iters = 20; + /** The fraction of data to use during iterative kmeans building. */ + double kmeans_trainset_fraction = 0.5; + /** + * By default (adaptive_centers = false), the cluster centers are trained in `ivf_flat::build`, + * and never modified in `ivf_flat::extend`. As a result, you may need to retrain the index + * from scratch after invoking (`ivf_flat::extend`) a few times with new data, the distribution of + * which is no longer representative of the original training set. + * + * The alternative behavior (adaptive_centers = true) is to update the cluster centers for new + * data when it is added. In this case, `index.centers()` are always exactly the centroids of the + * data in the corresponding clusters. The drawback of this behavior is that the centroids depend + * on the order of adding new data (through the classification of the added data); that is, + * `index.centers()` "drift" together with the changing distribution of the newly added data. + */ + bool adaptive_centers = false; + /** + * By default, the algorithm allocates more space than necessary for individual clusters + * (`list_data`). This allows to amortize the cost of memory allocation and reduce the number of + * data copies during repeated calls to `extend` (extending the database). + * + * The alternative is the conservative allocation behavior; when enabled, the algorithm always + * allocates the minimum amount of memory required to store the given number of records. Set this + * flag to `true` if you prefer to use as little GPU memory for the database as possible. + */ + bool conservative_memory_allocation = false; + + /** Build a raft IVF_FLAT index params from an existing cuvs IVF_FLAT index params. */ + operator raft::neighbors::ivf_flat::index_params() const + { + return raft::neighbors::ivf_flat::index_params{ + { + .metric = static_cast((int)this->metric), + .metric_arg = this->metric_arg, + .add_data_on_build = this->add_data_on_build, + }, + .n_lists = n_lists, + .kmeans_n_iters = kmeans_n_iters, + .kmeans_trainset_fraction = kmeans_trainset_fraction, + .adaptive_centers = adaptive_centers, + .conservative_memory_allocation = conservative_memory_allocation}; + } +}; +/** + * @} + */ + +/** + * @defgroup ivf_flat_cpp_search_params IVF-Flat index search parameters + * @{ + */ +struct search_params : ann::search_params { + /** The number of clusters to search. */ + uint32_t n_probes = 20; + + /** Build a raft IVF_FLAT search params from an existing cuvs IVF_FLAT search params. */ + operator raft::neighbors::ivf_flat::search_params() const + { + raft::neighbors::ivf_flat::search_params result = {{}, n_probes}; + return result; + } +}; +/** + * @} + */ + +/** + * @defgroup ivf_flat_cpp_index IVF-Flat index + * @{ + */ +/** + * @brief IVF-flat index. + * + * @tparam T data element type + * @tparam IdxT type of the indices in the source dataset + * + */ +template +struct index : ann::index { + static_assert(!raft::is_narrowing_v, + "IdxT must be able to represent all values of uint32_t"); + + public: + index(const index&) = delete; + index(index&&) = default; + index& operator=(const index&) = delete; + index& operator=(index&&) = default; + ~index() = default; + /** Construct an empty index. It needs to be trained and then populated. */ + index(raft::resources const& res, const index_params& params, uint32_t dim); + /** Construct an empty index. It needs to be trained and then populated. */ + index(raft::resources const& res, + cuvs::distance::DistanceType metric, + uint32_t n_lists, + bool adaptive_centers, + bool conservative_memory_allocation, + uint32_t dim); + index(raft::neighbors::ivf_flat::index&& raft_idx); + + /** + * Vectorized load/store size in elements, determines the size of interleaved data chunks. + */ + uint32_t veclen() const noexcept; + + /** Distance metric used for clustering. */ + cuvs::distance::DistanceType metric() const noexcept; + + /** Whether `centers()` change upon extending the index (ivf_flat::extend). */ + bool adaptive_centers() const noexcept; + + /** + * Inverted list data [size, dim]. + * + * The data consists of the dataset rows, grouped by their labels (into clusters/lists). + * Within each list (cluster), the data is grouped into blocks of `kIndexGroupSize` interleaved + * vectors. Note, the total index length is slightly larger than the source dataset length, + * because each cluster is padded by `kIndexGroupSize` elements. + * + * Interleaving pattern: + * within groups of `kIndexGroupSize` rows, the data is interleaved with the block size equal to + * `veclen * sizeof(T)`. That is, a chunk of `veclen` consecutive components of one row is + * followed by a chunk of the same size of the next row, and so on. + * + * __Example__: veclen = 2, dim = 6, kIndexGroupSize = 32, list_size = 31 + * + * x[ 0, 0], x[ 0, 1], x[ 1, 0], x[ 1, 1], ... x[14, 0], x[14, 1], x[15, 0], x[15, 1], + * x[16, 0], x[16, 1], x[17, 0], x[17, 1], ... x[30, 0], x[30, 1], - , - , + * x[ 0, 2], x[ 0, 3], x[ 1, 2], x[ 1, 3], ... x[14, 2], x[14, 3], x[15, 2], x[15, 3], + * x[16, 2], x[16, 3], x[17, 2], x[17, 3], ... x[30, 2], x[30, 3], - , - , + * x[ 0, 4], x[ 0, 5], x[ 1, 4], x[ 1, 5], ... x[14, 4], x[14, 5], x[15, 4], x[15, 5], + * x[16, 4], x[16, 5], x[17, 4], x[17, 5], ... x[30, 4], x[30, 5], - , - , + * + */ + /** Sizes of the lists (clusters) [n_lists] + * NB: This may differ from the actual list size if the shared lists have been extended by another + * index + */ + raft::device_vector_view list_sizes() noexcept; + raft::device_vector_view list_sizes() const noexcept; + + /** k-means cluster centers corresponding to the lists [n_lists, dim] */ + raft::device_matrix_view centers() noexcept; + raft::device_matrix_view centers() const noexcept; + + /** + * (Optional) Precomputed norms of the `centers` w.r.t. the chosen distance metric [n_lists]. + * + * NB: this may be empty if the index is empty or if the metric does not require the center norms + * calculation. + */ + std::optional> center_norms() noexcept; + std::optional> center_norms() const noexcept; + + /** Total length of the index. */ + IdxT size() const noexcept; + + /** Dimensionality of the data. */ + uint32_t dim() const noexcept; + + /** Number of clusters/inverted lists. */ + uint32_t n_lists() const noexcept; + raft::device_vector_view data_ptrs() noexcept; + raft::device_vector_view data_ptrs() const noexcept; + + /** Pointers to the inverted lists (clusters) indices [n_lists]. */ + raft::device_vector_view inds_ptrs() noexcept; + raft::device_vector_view inds_ptrs() const noexcept; + + /** + * Whether to use convervative memory allocation when extending the list (cluster) data + * (see index_params.conservative_memory_allocation). + */ + bool conservative_memory_allocation() const noexcept; + + /** Lists' data and indices. */ + std::vector>>& lists() noexcept; + const std::vector>>& lists() + const noexcept; + + // Get pointer to underlying RAFT index, not meant to be used outside of cuVS + inline raft::neighbors::ivf_flat::index* get_raft_index() noexcept + { + return raft_index_.get(); + } + inline const raft::neighbors::ivf_flat::index* get_raft_index() const noexcept + { + return raft_index_.get(); + } + + private: + std::unique_ptr> raft_index_; +}; +/** + * @} + */ + +/** + * @defgroup ivf_flat_cpp_index_build IVF-Flat index build + * @{ + */ +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_flat::index_params index_params; + * // create and fill the index from a [N, D] dataset + * auto index = ivf_flat::build(handle, dataset, index_params); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset a device pointer to a row-major matrix [n_rows, dim] + * + * @return the constructed ivf-flat index + */ +auto build(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::index_params& index_params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::ivf_flat::index; + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_flat::index_params index_params; + * // create and fill the index from a [N, D] dataset + * ivf_flat::index index; + * ivf_flat::build(handle, dataset, index_params, index); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset raft::device_matrix_view to a row-major matrix [n_rows, dim] + * @param[out] idx reference to ivf_flat::index + * + */ +void build(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::index_params& index_params, + raft::device_matrix_view dataset, + cuvs::neighbors::ivf_flat::index& idx); + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_flat::index_params index_params; + * // create and fill the index from a [N, D] dataset + * auto index = ivf_flat::build(handle, dataset, index_params); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset a device pointer to a row-major matrix [n_rows, dim] + * + * @return the constructed ivf-flat index + */ +auto build(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::index_params& index_params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::ivf_flat::index; + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_flat::index_params index_params; + * // create and fill the index from a [N, D] dataset + * ivf_flat::index index; + * ivf_flat::build(handle, dataset, index_params, index); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset raft::device_matrix_view to a row-major matrix [n_rows, dim] + * @param[out] idx reference to ivf_flat::index + * + */ +void build(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::index_params& index_params, + raft::device_matrix_view dataset, + cuvs::neighbors::ivf_flat::index& idx); + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_flat::index_params index_params; + * // create and fill the index from a [N, D] dataset + * auto index = ivf_flat::build(handle, dataset, index_params); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset a device pointer to a row-major matrix [n_rows, dim] + * + * @return the constructed ivf-flat index + */ +auto build(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::index_params& index_params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::ivf_flat::index; + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_flat::index_params index_params; + * // create and fill the index from a [N, D] dataset + * ivf_flat::index index; + * ivf_flat::build(handle, dataset, index_params, index); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset raft::device_matrix_view to a row-major matrix [n_rows, dim] + * @param[out] idx reference to ivf_flat::index + * + */ +void build(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::index_params& index_params, + raft::device_matrix_view dataset, + cuvs::neighbors::ivf_flat::index& idx); +/** + * @} + */ + +/** + * @defgroup ivf_flat_cpp_index_extend IVF-Flat index extend + * @{ + */ + +/** + * @brief Build a new index containing the data of the original plus new extra vectors. + * + * Implementation note: + * The new data is clustered according to existing kmeans clusters, then the cluster + * centers are adjusted to match the newly labeled data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_flat::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_flat::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * auto index = ivf_flat::extend(handle, new_vectors, no_op, index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors raft::device_matrix_view to a row-major matrix [n_rows, index.dim()] + * @param[in] new_indices optional raft::device_vector_view to a vector of indices [n_rows]. + * If the original index is empty (`orig_index.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[in] idx original index + * + * @return the constructed extended ivf-flat index + */ +auto extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + const cuvs::neighbors::ivf_flat::index& idx) + -> cuvs::neighbors::ivf_flat::index; + +/** + * @brief Extend the index in-place with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_flat::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_flat::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * ivf_flat::extend(handle, dataset, no_opt, &index_empty); + * @endcode + * + * + * @param[in] handle + * @param[in] new_vectors raft::device_matrix_view to a row-major matrix [n_rows, index.dim()] + * @param[in] new_indices optional raft::device_vector_view to a vector of indices [n_rows]. + * If the original index is empty (`orig_index.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx pointer to index, to be overwritten in-place + */ +void extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + cuvs::neighbors::ivf_flat::index* idx); + +/** + * @brief Build a new index containing the data of the original plus new extra vectors. + * + * Implementation note: + * The new data is clustered according to existing kmeans clusters, then the cluster + * centers are adjusted to match the newly labeled data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_flat::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_flat::build(handle, dataset, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * auto index = ivf_flat::extend(handle, new_vectors, no_op, index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors raft::device_matrix_view to a row-major matrix [n_rows, index.dim()] + * @param[in] new_indices optional raft::device_vector_view to a vector of indices [n_rows]. + * If the original index is empty (`orig_index.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[in] idx original index + * + * @return the constructed extended ivf-flat index + */ +auto extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + const cuvs::neighbors::ivf_flat::index& idx) + -> cuvs::neighbors::ivf_flat::index; + +/** + * @brief Extend the index in-place with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_flat::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_flat::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * ivf_flat::extend(handle, dataset, no_opt, &index_empty); + * @endcode + * + * + * @param[in] handle + * @param[in] new_vectors raft::device_matrix_view to a row-major matrix [n_rows, index.dim()] + * @param[in] new_indices optional raft::device_vector_view to a vector of indices [n_rows]. + * If the original index is empty (`orig_index.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx pointer to index, to be overwritten in-place + */ +void extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + cuvs::neighbors::ivf_flat::index* idx); + +/** + * @brief Build a new index containing the data of the original plus new extra vectors. + * + * Implementation note: + * The new data is clustered according to existing kmeans clusters, then the cluster + * centers are adjusted to match the newly labeled data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_flat::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_flat::build(handle, dataset, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * auto index = ivf_flat::extend(handle, new_vectors, no_op, index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors raft::device_matrix_view to a row-major matrix [n_rows, index.dim()] + * @param[in] new_indices optional raft::device_vector_view to a vector of indices [n_rows]. + * If the original index is empty (`orig_index.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[in] idx original index + * + * @return the constructed extended ivf-flat index + */ +auto extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + const cuvs::neighbors::ivf_flat::index& idx) + -> cuvs::neighbors::ivf_flat::index; + +/** + * @brief Extend the index in-place with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_flat::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_flat::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * ivf_flat::extend(handle, dataset, no_opt, &index_empty); + * @endcode + * + * + * @param[in] handle + * @param[in] new_vectors raft::device_matrix_view to a row-major matrix [n_rows, index.dim()] + * @param[in] new_indices optional raft::device_vector_view to a vector of indices [n_rows]. + * If the original index is empty (`orig_index.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx pointer to index, to be overwritten in-place + */ +void extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + cuvs::neighbors::ivf_flat::index* idx); +/** + * @} + */ + +/** + * @defgroup ivf_flat_cpp_index_search IVF-Flat index search + * @{ + */ + +/** + * @brief Search ANN using the constructed index. + * + * See the [ivf_flat::build](#ivf_flat::build) documentation for a usage example. + * + * Note, this function requires a temporary buffer to store intermediate results between cuda kernel + * calls, which may lead to undesirable allocations and slowdown. To alleviate the problem, you can + * pass a pool memory resource or a large enough pre-allocated memory resource to reduce or + * eliminate entirely allocations happening within `search`: + * @code{.cpp} + * ... + * // use default search parameters + * ivf_flat::search_params search_params; + * // Use the same allocator across multiple searches to reduce the number of + * // cuda memory allocations + * ivf_flat::search(handle, search_params, index, queries1, out_inds1, out_dists1); + * ivf_flat::search(handle, search_params, index, queries2, out_inds2, out_dists2); + * ivf_flat::search(handle, search_params, index, queries3, out_inds3, out_dists3); + * ... + * @endcode + * + * @param[in] handle + * @param[in] params configure the search + * @param[in] index ivf-flat constructed index + * @param[in] queries raft::device_matrix_view to a row-major matrix [n_queries, index->dim()] + * @param[out] neighbors raft::device_matrix_view to the indices of the neighbors in the source + * dataset [n_queries, k] + * @param[out] distances raft::device_matrix_view to the distances to the selected neighbors + * [n_queries, k] + */ +void search(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::search_params& params, + cuvs::neighbors::ivf_flat::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); + +/** + * @brief Search ANN using the constructed index. + * + * See the [ivf_flat::build](#ivf_flat::build) documentation for a usage example. + * + * Note, this function requires a temporary buffer to store intermediate results between cuda kernel + * calls, which may lead to undesirable allocations and slowdown. To alleviate the problem, you can + * pass a pool memory resource or a large enough pre-allocated memory resource to reduce or + * eliminate entirely allocations happening within `search`: + * @code{.cpp} + * ... + * // use default search parameters + * ivf_flat::search_params search_params; + * // Use the same allocator across multiple searches to reduce the number of + * // cuda memory allocations + * ivf_flat::search(handle, search_params, index, queries1, out_inds1, out_dists1); + * ivf_flat::search(handle, search_params, index, queries2, out_inds2, out_dists2); + * ivf_flat::search(handle, search_params, index, queries3, out_inds3, out_dists3); + * ... + * @endcode + * + * @param[in] handle + * @param[in] params configure the search + * @param[in] index ivf-flat constructed index + * @param[in] queries raft::device_matrix_view to a row-major matrix [n_queries, index->dim()] + * @param[out] neighbors raft::device_matrix_view to the indices of the neighbors in the source + * dataset [n_queries, k] + * @param[out] distances raft::device_matrix_view to the distances to the selected neighbors + * [n_queries, k] + */ +void search(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::search_params& params, + cuvs::neighbors::ivf_flat::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); + +/** + * @brief Search ANN using the constructed index. + * + * See the [ivf_flat::build](#ivf_flat::build) documentation for a usage example. + * + * Note, this function requires a temporary buffer to store intermediate results between cuda kernel + * calls, which may lead to undesirable allocations and slowdown. To alleviate the problem, you can + * pass a pool memory resource or a large enough pre-allocated memory resource to reduce or + * eliminate entirely allocations happening within `search`: + * @code{.cpp} + * ... + * // use default search parameters + * ivf_flat::search_params search_params; + * // Use the same allocator across multiple searches to reduce the number of + * // cuda memory allocations + * ivf_flat::search(handle, search_params, index, queries1, out_inds1, out_dists1); + * ivf_flat::search(handle, search_params, index, queries2, out_inds2, out_dists2); + * ivf_flat::search(handle, search_params, index, queries3, out_inds3, out_dists3); + * ... + * @endcode + * + * @param[in] handle + * @param[in] params configure the search + * @param[in] index ivf-flat constructed index + * @param[in] queries raft::device_matrix_view to a row-major matrix [n_queries, index->dim()] + * @param[out] neighbors raft::device_matrix_view to the indices of the neighbors in the source + * dataset [n_queries, k] + * @param[out] distances raft::device_matrix_view to the distances to the selected neighbors + * [n_queries, k] + */ +void search(raft::resources const& handle, + const cuvs::neighbors::ivf_flat::search_params& params, + cuvs::neighbors::ivf_flat::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); +/** + * @} + */ + +/** + * @defgroup ivf_flat_cpp_serialize IVF-Flat index serialize + * @{ + */ + +/** + * Save the index to file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * // create an index with `auto index = ivf_flat::build(...);` + * cuvs::serialize_file(handle, filename, index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the file name for saving the index + * @param[in] index IVF-Flat index + * + */ +void serialize_file(raft::resources const& handle, + const std::string& filename, + const cuvs::neighbors::ivf_flat::index& index); + +/** + * Load index from file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * using T = float; // data element type + * using IdxT = int64_t; // type of the index + * // create an empty index with `ivf_pq::index index(handle, index_params, dim);` + * cuvs::deserialize_file(handle, filename, &index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the name of the file that stores the index + * @param[in] index IVF-Flat index + * + */ +void deserialize_file(raft::resources const& handle, + const std::string& filename, + cuvs::neighbors::ivf_flat::index* index); + +/** + * Write the index to an output string + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create an output string + * std::string str; + * // create an index with `auto index = ivf_flat::build(...);` + * cuvs::serialize(handle, str, index); + * @endcode + * + * @param[in] handle the raft handle + * @param[out] str output string + * @param[in] index IVF-Flat index + * + */ +void serialize(raft::resources const& handle, + std::string& str, + const cuvs::neighbors::ivf_flat::index& index); + +/** + * Load index from input string + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create an input string + * std::string str; + * using T = float; // data element type + * using IdxT = int64_t; // type of the index + * // create an empty index with `ivf_pq::index index(handle, index_params, dim);` + * auto index = cuvs::deserialize(handle, str, &index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] str output string + * @param[in] index IVF-Flat index + * + */ +void deserialize(raft::resources const& handle, + const std::string& str, + cuvs::neighbors::ivf_flat::index* index); + +/** + * Save the index to file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * // create an index with `auto index = ivf_flat::build(...);` + * cuvs::serialize_file(handle, filename, index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the file name for saving the index + * @param[in] index IVF-Flat index + * + */ +void serialize_file(raft::resources const& handle, + const std::string& filename, + const cuvs::neighbors::ivf_flat::index& index); + +/** + * Load index from file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * using T = float; // data element type + * using IdxT = int64_t; // type of the index + * // create an empty index with `ivf_pq::index index(handle, index_params, dim);` + * cuvs::deserialize_file(handle, filename, &index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the name of the file that stores the index + * @param[in] index IVF-Flat index + * + */ +void deserialize_file(raft::resources const& handle, + const std::string& filename, + cuvs::neighbors::ivf_flat::index* index); + +/** + * Write the index to an output string + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create an output string + * std::string str; + * // create an index with `auto index = ivf_flat::build(...);` + * cuvs::serialize(handle, str, index); + * @endcode + * + * @param[in] handle the raft handle + * @param[out] str output string + * @param[in] index IVF-Flat index + * + */ +void serialize(raft::resources const& handle, + std::string& str, + const cuvs::neighbors::ivf_flat::index& index); + +/** + * Load index from input string + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create an input string + * std::string str; + * using T = float; // data element type + * using IdxT = int64_t; // type of the index + * // create an empty index with `ivf_pq::index index(handle, index_params, dim);` + * auto index = cuvs::deserialize(handle, str, &index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] str output string + * @param[in] index IVF-Flat index + * + */ +void deserialize(raft::resources const& handle, + const std::string& str, + cuvs::neighbors::ivf_flat::index* index); + +/** + * Save the index to file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * // create an index with `auto index = ivf_flat::build(...);` + * cuvs::serialize_file(handle, filename, index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the file name for saving the index + * @param[in] index IVF-Flat index + * + */ +void serialize_file(raft::resources const& handle, + const std::string& filename, + const cuvs::neighbors::ivf_flat::index& index); + +/** + * Load index from file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * using T = float; // data element type + * using IdxT = int64_t; // type of the index + * // create an empty index with `ivf_pq::index index(handle, index_params, dim);` + * cuvs::deserialize_file(handle, filename, &index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the name of the file that stores the index + * @param[in] index IVF-Flat index + * + */ +void deserialize_file(raft::resources const& handle, + const std::string& filename, + cuvs::neighbors::ivf_flat::index* index); + +/** + * Write the index to an output string + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create an output string + * std::string str; + * // create an index with `auto index = ivf_flat::build(...);` + * cuvs::serialize(handle, str, index); + * @endcode + * + * @param[in] handle the raft handle + * @param[out] str output string + * @param[in] index IVF-Flat index + * + */ +void serialize(raft::resources const& handle, + std::string& str, + const cuvs::neighbors::ivf_flat::index& index); + +/** + * Load index from input string + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * #include + * + * raft::resources handle; + * + * // create an input string + * std::string str; + * using T = float; // data element type + * using IdxT = int64_t; // type of the index + * // create an empty index with `ivf_pq::index index(handle, index_params, dim);` + * auto index = cuvs::deserialize(handle, str, &index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] str output string + * @param[in] index IVF-Flat index + * + */ +void deserialize(raft::resources const& handle, + const std::string& str, + cuvs::neighbors::ivf_flat::index* index); + +/** + * @} + */ +} // namespace cuvs::neighbors::ivf_flat \ No newline at end of file diff --git a/cpp/include/cuvs/neighbors/ivf_pq.h b/cpp/include/cuvs/neighbors/ivf_pq.h new file mode 100644 index 0000000000..c1fcaed86a --- /dev/null +++ b/cpp/include/cuvs/neighbors/ivf_pq.h @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup ivf_pq_c_index_params IVF-PQ index build parameters + * @{ + */ +/** + * @brief A type for specifying how PQ codebooks are created + * + */ +enum codebook_gen { // NOLINT + PER_SUBSPACE = 0, // NOLINT + PER_CLUSTER = 1, // NOLINT +}; + +/** + * @brief Supplemental parameters to build IVF-PQ Index + * + */ +struct ivfPqIndexParams { + /** Distance type. */ + enum DistanceType metric; + /** The argument used by some distance metrics. */ + float metric_arg; + /** + * Whether to add the dataset content to the index, i.e.: + * + * - `true` means the index is filled with the dataset vectors and ready to search after calling + * `build`. + * - `false` means `build` only trains the underlying model (e.g. quantizer or clustering), but + * the index is left empty; you'd need to call `extend` on the index afterwards to populate it. + */ + bool add_data_on_build; + /** + * The number of inverted lists (clusters) + * + * Hint: the number of vectors per cluster (`n_rows/n_lists`) should be approximately 1,000 to + * 10,000. + */ + uint32_t n_lists; + /** The number of iterations searching for kmeans centers (index building). */ + uint32_t kmeans_n_iters; + /** The fraction of data to use during iterative kmeans building. */ + double kmeans_trainset_fraction; + /** + * The bit length of the vector element after compression by PQ. + * + * Possible values: [4, 5, 6, 7, 8]. + * + * Hint: the smaller the 'pq_bits', the smaller the index size and the better the search + * performance, but the lower the recall. + */ + uint32_t pq_bits; + /** + * The dimensionality of the vector after compression by PQ. When zero, an optimal value is + * selected using a heuristic. + * + * NB: `pq_dim * pq_bits` must be a multiple of 8. + * + * Hint: a smaller 'pq_dim' results in a smaller index size and better search performance, but + * lower recall. If 'pq_bits' is 8, 'pq_dim' can be set to any number, but multiple of 8 are + * desirable for good performance. If 'pq_bits' is not 8, 'pq_dim' should be a multiple of 8. + * For good performance, it is desirable that 'pq_dim' is a multiple of 32. Ideally, 'pq_dim' + * should be also a divisor of the dataset dim. + */ + uint32_t pq_dim; + /** How PQ codebooks are created. */ + enum codebook_gen codebook_kind; + /** + * Apply a random rotation matrix on the input data and queries even if `dim % pq_dim == 0`. + * + * Note: if `dim` is not multiple of `pq_dim`, a random rotation is always applied to the input + * data and queries to transform the working space from `dim` to `rot_dim`, which may be slightly + * larger than the original space and and is a multiple of `pq_dim` (`rot_dim % pq_dim == 0`). + * However, this transform is not necessary when `dim` is multiple of `pq_dim` + * (`dim == rot_dim`, hence no need in adding "extra" data columns / features). + * + * By default, if `dim == rot_dim`, the rotation transform is initialized with the identity + * matrix. When `force_random_rotation == true`, a random orthogonal transform matrix is generated + * regardless of the values of `dim` and `pq_dim`. + */ + bool force_random_rotation; + /** + * By default, the algorithm allocates more space than necessary for individual clusters + * (`list_data`). This allows to amortize the cost of memory allocation and reduce the number of + * data copies during repeated calls to `extend` (extending the database). + * + * The alternative is the conservative allocation behavior; when enabled, the algorithm always + * allocates the minimum amount of memory required to store the given number of records. Set this + * flag to `true` if you prefer to use as little GPU memory for the database as possible. + */ + bool conservative_memory_allocation; +}; + +typedef struct ivfPqIndexParams* cuvsIvfPqIndexParams_t; + +/** + * @brief Allocate IVF-PQ Index params, and populate with default values + * + * @param[in] index_params cuvsIvfPqIndexParams_t to allocate + * @return cuvsError_t + */ +cuvsError_t cuvsIvfPqIndexParamsCreate(cuvsIvfPqIndexParams_t* index_params); + +/** + * @brief De-allocate IVF-PQ Index params + * + * @param[in] index_params + * @return cuvsError_t + */ +cuvsError_t cuvsIvfPqIndexParamsDestroy(cuvsIvfPqIndexParams_t index_params); +/** + * @} + */ + +/** + * @defgroup ivf_pq_c_search_params IVF-PQ index search parameters + * @{ + */ +/** + * @brief Supplemental parameters to search IVF-PQ index + * + */ +struct ivfPqSearchParams { + /** The number of clusters to search. */ + uint32_t n_probes; + /** + * Data type of look up table to be created dynamically at search time. + * + * Possible values: [CUDA_R_32F, CUDA_R_16F, CUDA_R_8U] + * + * The use of low-precision types reduces the amount of shared memory required at search time, so + * fast shared memory kernels can be used even for datasets with large dimansionality. Note that + * the recall is slightly degraded when low-precision type is selected. + */ + cudaDataType_t lut_dtype; + /** + * Storage data type for distance/similarity computed at search time. + * + * Possible values: [CUDA_R_16F, CUDA_R_32F] + * + * If the performance limiter at search time is device memory access, selecting FP16 will improve + * performance slightly. + */ + cudaDataType_t internal_distance_dtype; + /** + * Preferred fraction of SM's unified memory / L1 cache to be used as shared memory. + * + * Possible values: [0.0 - 1.0] as a fraction of the `sharedMemPerMultiprocessor`. + * + * One wants to increase the carveout to make sure a good GPU occupancy for the main search + * kernel, but not to keep it too high to leave some memory to be used as L1 cache. Note, this + * value is interpreted only as a hint. Moreover, a GPU usually allows only a fixed set of cache + * configurations, so the provided value is rounded up to the nearest configuration. Refer to the + * NVIDIA tuning guide for the target GPU architecture. + * + * Note, this is a low-level tuning parameter that can have drastic negative effects on the search + * performance if tweaked incorrectly. + */ + double preferred_shmem_carveout; +}; + +typedef struct ivfPqSearchParams* cuvsIvfPqSearchParams_t; + +/** + * @brief Allocate IVF-PQ search params, and populate with default values + * + * @param[in] params cuvsIvfPqSearchParams_t to allocate + * @return cuvsError_t + */ +cuvsError_t cuvsIvfPqSearchParamsCreate(cuvsIvfPqSearchParams_t* params); + +/** + * @brief De-allocate IVF-PQ search params + * + * @param[in] params + * @return cuvsError_t + */ +cuvsError_t cuvsIvfPqSearchParamsDestroy(cuvsIvfPqSearchParams_t params); +/** + * @} + */ + +/** + * @defgroup ivf_pq_c_index IVF-PQ index + * @{ + */ +/** + * @brief Struct to hold address of cuvs::neighbors::ivf_pq::index and its active trained dtype + * + */ +typedef struct { + uintptr_t addr; + DLDataType dtype; +} ivfPqIndex; + +typedef ivfPqIndex* cuvsIvfPqIndex_t; + +/** + * @brief Allocate IVF-PQ index + * + * @param[in] index cuvsIvfPqIndex_t to allocate + * @return ivfPqError_t + */ +cuvsError_t ivfPqIndexCreate(cuvsIvfPqIndex_t* index); + +/** + * @brief De-allocate IVF-PQ index + * + * @param[in] index cuvsIvfPqIndex_t to de-allocate + */ +cuvsError_t ivfPqIndexDestroy(cuvsIvfPqIndex_t index); +/** + * @} + */ + +/** + * @defgroup ivf_pq_c_index_build IVF-PQ index build + * @{ + */ +/** + * @brief Build a IVF-PQ index with a `DLManagedTensor` which has underlying + * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`, + * or `kDLCPU`. Also, acceptable underlying types are: + * 1. `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * 2. `kDLDataType.code == kDLInt` and `kDLDataType.bits = 8` + * 3. `kDLDataType.code == kDLUInt` and `kDLDataType.bits = 8` + * + * @code {.c} + * #include + * #include + * + * // Create cuvsResources_t + * cuvsResources_t res; + * cuvsError_t res_create_status = cuvsResourcesCreate(&res); + * + * // Assume a populated `DLManagedTensor` type here + * DLManagedTensor dataset; + * + * // Create default index params + * cuvsIvfPqIndexParams_t index_params; + * cuvsError_t params_create_status = cuvsIvfPqIndexParamsCreate(&index_params); + * + * // Create IVF-PQ index + * cuvsIvfPqIndex_t index; + * cuvsError_t index_create_status = ivfPqIndexCreate(&index); + * + * // Build the IVF-PQ Index + * cuvsError_t build_status = ivfPqBuild(res, index_params, &dataset, index); + * + * // de-allocate `index_params`, `index` and `res` + * cuvsError_t params_destroy_status = cuvsIvfPqIndexParamsDestroy(index_params); + * cuvsError_t index_destroy_status = ivfPqIndexDestroy(index); + * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); + * @endcode + * + * @param[in] res cuvsResources_t opaque C handle + * @param[in] params cuvsIvfPqIndexParams_t used to build IVF-PQ index + * @param[in] dataset DLManagedTensor* training dataset + * @param[out] index cuvsIvfPqIndex_t Newly built IVF-PQ index + * @return cuvsError_t + */ +cuvsError_t ivfPqBuild(cuvsResources_t res, + cuvsIvfPqIndexParams_t params, + DLManagedTensor* dataset, + cuvsIvfPqIndex_t index); +/** + * @} + */ + +/** + * @defgroup ivf_pq_c_index_search IVF-PQ index search + * @{ + */ +/** + * @brief Search a IVF-PQ index with a `DLManagedTensor` which has underlying + * `DLDeviceType` equal to `kDLCUDA`, `kDLCUDAHost`, `kDLCUDAManaged`. + * It is also important to note that the IVF-PQ Index must have been built + * with the same type of `queries`, such that `index.dtype.code == + * queries.dl_tensor.dtype.code` Types for input are: + * 1. `queries`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * 2. `neighbors`: `kDLDataType.code == kDLUInt` and `kDLDataType.bits = 32` + * 3. `distances`: `kDLDataType.code == kDLFloat` and `kDLDataType.bits = 32` + * + * @code {.c} + * #include + * #include + * + * // Create cuvsResources_t + * cuvsResources_t res; + * cuvsError_t res_create_status = cuvsResourcesCreate(&res); + * + * // Assume a populated `DLManagedTensor` type here + * DLManagedTensor dataset; + * DLManagedTensor queries; + * DLManagedTensor neighbors; + * + * // Create default search params + * cuvsIvfPqSearchParams_t search_params; + * cuvsError_t params_create_status = cuvsIvfPqSearchParamsCreate(&search_params); + * + * // Search the `index` built using `ivfPqBuild` + * cuvsError_t search_status = ivfPqSearch(res, search_params, index, &queries, &neighbors, + * &distances); + * + * // de-allocate `search_params` and `res` + * cuvsError_t params_destroy_status = cuvsIvfPqSearchParamsDestroy(search_params); + * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); + * @endcode + * + * @param[in] res cuvsResources_t opaque C handle + * @param[in] search_params cuvsIvfPqSearchParams_t used to search IVF-PQ index + * @param[in] index ivfPqIndex which has been returned by `ivfPqBuild` + * @param[in] queries DLManagedTensor* queries dataset to search + * @param[out] neighbors DLManagedTensor* output `k` neighbors for queries + * @param[out] distances DLManagedTensor* output `k` distances for queries + */ +cuvsError_t ivfPqSearch(cuvsResources_t res, + cuvsIvfPqSearchParams_t search_params, + cuvsIvfPqIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances); +/** + * @} + */ + +#ifdef __cplusplus +} +#endif diff --git a/cpp/include/cuvs/neighbors/ivf_pq.hpp b/cpp/include/cuvs/neighbors/ivf_pq.hpp new file mode 100644 index 0000000000..b2fc9a3668 --- /dev/null +++ b/cpp/include/cuvs/neighbors/ivf_pq.hpp @@ -0,0 +1,868 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "ann_types.hpp" +#include + +namespace cuvs::neighbors::ivf_pq { + +/** + * @defgroup ivf_pq_cpp_index_params IVF-PQ index build parameters + * @{ + */ +/** A type for specifying how PQ codebooks are created. */ +enum class codebook_gen { // NOLINT + PER_SUBSPACE = 0, // NOLINT + PER_CLUSTER = 1, // NOLINT +}; + +struct index_params : ann::index_params { + /** + * The number of inverted lists (clusters) + * + * Hint: the number of vectors per cluster (`n_rows/n_lists`) should be approximately 1,000 to + * 10,000. + */ + uint32_t n_lists = 1024; + /** The number of iterations searching for kmeans centers (index building). */ + uint32_t kmeans_n_iters = 20; + /** The fraction of data to use during iterative kmeans building. */ + double kmeans_trainset_fraction = 0.5; + /** + * The bit length of the vector element after compression by PQ. + * + * Possible values: [4, 5, 6, 7, 8]. + * + * Hint: the smaller the 'pq_bits', the smaller the index size and the better the search + * performance, but the lower the recall. + */ + uint32_t pq_bits = 8; + /** + * The dimensionality of the vector after compression by PQ. When zero, an optimal value is + * selected using a heuristic. + * + * NB: `pq_dim * pq_bits` must be a multiple of 8. + * + * Hint: a smaller 'pq_dim' results in a smaller index size and better search performance, but + * lower recall. If 'pq_bits' is 8, 'pq_dim' can be set to any number, but multiple of 8 are + * desirable for good performance. If 'pq_bits' is not 8, 'pq_dim' should be a multiple of 8. + * For good performance, it is desirable that 'pq_dim' is a multiple of 32. Ideally, 'pq_dim' + * should be also a divisor of the dataset dim. + */ + uint32_t pq_dim = 0; + /** How PQ codebooks are created. */ + codebook_gen codebook_kind = codebook_gen::PER_SUBSPACE; + /** + * Apply a random rotation matrix on the input data and queries even if `dim % pq_dim == 0`. + * + * Note: if `dim` is not multiple of `pq_dim`, a random rotation is always applied to the input + * data and queries to transform the working space from `dim` to `rot_dim`, which may be slightly + * larger than the original space and and is a multiple of `pq_dim` (`rot_dim % pq_dim == 0`). + * However, this transform is not necessary when `dim` is multiple of `pq_dim` + * (`dim == rot_dim`, hence no need in adding "extra" data columns / features). + * + * By default, if `dim == rot_dim`, the rotation transform is initialized with the identity + * matrix. When `force_random_rotation == true`, a random orthogonal transform matrix is generated + * regardless of the values of `dim` and `pq_dim`. + */ + bool force_random_rotation = false; + /** + * By default, the algorithm allocates more space than necessary for individual clusters + * (`list_data`). This allows to amortize the cost of memory allocation and reduce the number of + * data copies during repeated calls to `extend` (extending the database). + * + * The alternative is the conservative allocation behavior; when enabled, the algorithm always + * allocates the minimum amount of memory required to store the given number of records. Set this + * flag to `true` if you prefer to use as little GPU memory for the database as possible. + */ + bool conservative_memory_allocation = false; + + /** Build a raft IVF_PQ index params from an existing cuvs IVF_PQ index params. */ + operator raft::neighbors::ivf_pq::index_params() const + { + return raft::neighbors::ivf_pq::index_params{ + { + .metric = static_cast((int)this->metric), + .metric_arg = this->metric_arg, + .add_data_on_build = this->add_data_on_build, + }, + .n_lists = n_lists, + .kmeans_n_iters = kmeans_n_iters, + .kmeans_trainset_fraction = kmeans_trainset_fraction, + .pq_bits = pq_bits, + .pq_dim = pq_dim, + .codebook_kind = static_cast((int)this->codebook_kind), + .force_random_rotation = force_random_rotation, + .conservative_memory_allocation = conservative_memory_allocation}; + } +}; +/** + * @} + */ + +/** + * @defgroup ivf_pq_cpp_search_params IVF-PQ index search parameters + * @{ + */ +struct search_params : ann::search_params { + /** The number of clusters to search. */ + uint32_t n_probes = 20; + /** + * Data type of look up table to be created dynamically at search time. + * + * Possible values: [CUDA_R_32F, CUDA_R_16F, CUDA_R_8U] + * + * The use of low-precision types reduces the amount of shared memory required at search time, so + * fast shared memory kernels can be used even for datasets with large dimansionality. Note that + * the recall is slightly degraded when low-precision type is selected. + */ + cudaDataType_t lut_dtype = CUDA_R_32F; + /** + * Storage data type for distance/similarity computed at search time. + * + * Possible values: [CUDA_R_16F, CUDA_R_32F] + * + * If the performance limiter at search time is device memory access, selecting FP16 will improve + * performance slightly. + */ + cudaDataType_t internal_distance_dtype = CUDA_R_32F; + /** + * Preferred fraction of SM's unified memory / L1 cache to be used as shared memory. + * + * Possible values: [0.0 - 1.0] as a fraction of the `sharedMemPerMultiprocessor`. + * + * One wants to increase the carveout to make sure a good GPU occupancy for the main search + * kernel, but not to keep it too high to leave some memory to be used as L1 cache. Note, this + * value is interpreted only as a hint. Moreover, a GPU usually allows only a fixed set of cache + * configurations, so the provided value is rounded up to the nearest configuration. Refer to the + * NVIDIA tuning guide for the target GPU architecture. + * + * Note, this is a low-level tuning parameter that can have drastic negative effects on the search + * performance if tweaked incorrectly. + */ + double preferred_shmem_carveout = 1.0; + + /** Build a raft IVF_PQ search params from an existing cuvs IVF_PQ search params. */ + operator raft::neighbors::ivf_pq::search_params() const + { + raft::neighbors::ivf_pq::search_params result = { + {}, n_probes, lut_dtype, internal_distance_dtype, preferred_shmem_carveout}; + return result; + } +}; +/** + * @} + */ + +template +using list_data = raft::neighbors::ivf_pq::list_data; + +/** + * @defgroup ivf_pq_cpp_index IVF-PQ index + * @{ + */ +/** + * @brief IVF-PQ index. + * + * In the IVF-PQ index, a database vector y is approximated with two level quantization: + * + * y = Q_1(y) + Q_2(y - Q_1(y)) + * + * The first level quantizer (Q_1), maps the vector y to the nearest cluster center. The number of + * clusters is n_lists. + * + * The second quantizer encodes the residual, and it is defined as a product quantizer [1]. + * + * A product quantizer encodes a `dim` dimensional vector with a `pq_dim` dimensional vector. + * First we split the input vector into `pq_dim` subvectors (denoted by u), where each u vector + * contains `pq_len` distinct components of y + * + * y_1, y_2, ... y_{pq_len}, y_{pq_len+1}, ... y_{2*pq_len}, ... y_{dim-pq_len+1} ... y_{dim} + * \___________________/ \____________________________/ \______________________/ + * u_1 u_2 u_{pq_dim} + * + * Then each subvector encoded with a separate quantizer q_i, end the results are concatenated + * + * Q_2(y) = q_1(u_1),q_2(u_2),...,q_{pq_dim}(u_pq_dim}) + * + * Each quantizer q_i outputs a code with pq_bit bits. The second level quantizers are also defined + * by k-means clustering in the corresponding sub-space: the reproduction values are the centroids, + * and the set of reproduction values is the codebook. + * + * When the data dimensionality `dim` is not multiple of `pq_dim`, the feature space is transformed + * using a random orthogonal matrix to have `rot_dim = pq_dim * pq_len` dimensions + * (`rot_dim >= dim`). + * + * The second-level quantizers are trained either for each subspace or for each cluster: + * (a) codebook_gen::PER_SUBSPACE: + * creates `pq_dim` second-level quantizers - one for each slice of the data along features; + * (b) codebook_gen::PER_CLUSTER: + * creates `n_lists` second-level quantizers - one for each first-level cluster. + * In either case, the centroids are again found using k-means clustering interpreting the data as + * having pq_len dimensions. + * + * [1] Product quantization for nearest neighbor search Herve Jegou, Matthijs Douze, Cordelia Schmid + * + * @tparam IdxT type of the indices in the source dataset + * + */ +template +struct index : ann::index { + static_assert(!raft::is_narrowing_v, + "IdxT must be able to represent all values of uint32_t"); + + using pq_centers_extents = typename raft::neighbors::ivf_pq::index::pq_centers_extents; + + public: + index(const index&) = delete; + index(index&&) = default; + auto operator=(const index&) -> index& = delete; + auto operator=(index&&) -> index& = default; + ~index() = default; + + /** Construct an empty index. It needs to be trained and then populated. */ + index(raft::resources const& handle, const index_params& params, uint32_t dim); + index(raft::neighbors::ivf_pq::index&& raft_idx); + + /** Total length of the index. */ + IdxT size() const noexcept; + + /** Dimensionality of the input data. */ + uint32_t dim() const noexcept; + + /** + * Dimensionality of the cluster centers: + * input data dim extended with vector norms and padded to 8 elems. + */ + uint32_t dim_ext() const noexcept; + + /** + * Dimensionality of the data after transforming it for PQ processing + * (rotated and augmented to be muplitple of `pq_dim`). + */ + uint32_t rot_dim() const noexcept; + + /** The bit length of an encoded vector element after compression by PQ. */ + uint32_t pq_bits() const noexcept; + + /** The dimensionality of an encoded vector after compression by PQ. */ + uint32_t pq_dim() const noexcept; + + /** Dimensionality of a subspaces, i.e. the number of vector components mapped to a subspace */ + uint32_t pq_len() const noexcept; + + /** The number of vectors in a PQ codebook (`1 << pq_bits`). */ + uint32_t pq_book_size() const noexcept; + + /** Distance metric used for clustering. */ + cuvs::distance::DistanceType metric() const noexcept; + + /** How PQ codebooks are created. */ + codebook_gen codebook_kind() const noexcept; + + /** Number of clusters/inverted lists (first level quantization). */ + uint32_t n_lists() const noexcept; + + /** + * Whether to use convervative memory allocation when extending the list (cluster) data + * (see index_params.conservative_memory_allocation). + */ + bool conservative_memory_allocation() const noexcept; + + /** + * PQ cluster centers + * + * - codebook_gen::PER_SUBSPACE: [pq_dim , pq_len, pq_book_size] + * - codebook_gen::PER_CLUSTER: [n_lists, pq_len, pq_book_size] + */ + raft::mdspan pq_centers() noexcept; + raft::mdspan pq_centers() const noexcept; + + /** Lists' data and indices. */ + std::vector>>& lists() noexcept; + const std::vector>>& lists() const noexcept; + + /** Pointers to the inverted lists (clusters) data [n_lists]. */ + raft::device_vector_view data_ptrs() noexcept; + raft::device_vector_view data_ptrs() + const noexcept; + + /** Pointers to the inverted lists (clusters) indices [n_lists]. */ + raft::device_vector_view inds_ptrs() noexcept; + raft::device_vector_view inds_ptrs() const noexcept; + + /** The transform matrix (original space -> rotated padded space) [rot_dim, dim] */ + raft::device_matrix_view rotation_matrix() noexcept; + raft::device_matrix_view rotation_matrix() const noexcept; + + /** + * Accumulated list sizes, sorted in descending order [n_lists + 1]. + * The last value contains the total length of the index. + * The value at index zero is always zero. + * + * That is, the content of this span is as if the `list_sizes` was sorted and then accumulated. + * + * This span is used during search to estimate the maximum size of the workspace. + */ + raft::host_vector_view accum_sorted_sizes() noexcept; + raft::host_vector_view accum_sorted_sizes() const noexcept; + + /** Sizes of the lists [n_lists]. */ + raft::device_vector_view list_sizes() noexcept; + raft::device_vector_view list_sizes() const noexcept; + + /** Cluster centers corresponding to the lists in the original space [n_lists, dim_ext] */ + raft::device_matrix_view centers() noexcept; + raft::device_matrix_view centers() const noexcept; + + /** Cluster centers corresponding to the lists in the rotated space [n_lists, rot_dim] */ + raft::device_matrix_view centers_rot() noexcept; + raft::device_matrix_view centers_rot() const noexcept; + + // Get pointer to underlying RAFT index, not meant to be used outside of cuVS + inline raft::neighbors::ivf_pq::index* get_raft_index() noexcept + { + return raft_index_.get(); + } + inline const raft::neighbors::ivf_pq::index* get_raft_index() const noexcept + { + return raft_index_.get(); + } + + private: + std::unique_ptr> raft_index_; +}; +/** + * @} + */ + +/** + * @defgroup ivf_pq_cpp_index_build IVF-PQ index build + * @{ + */ +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_pq::index_params index_params; + * // create and fill the index from a [N, D] dataset + * auto index = ivf_pq::build(handle, index_params, dataset); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset a device matrix view to a row-major matrix [n_rows, dim] + * + * @return the constructed ivf-pq index + */ +auto build(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::index_params& index_params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::ivf_pq::index; + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_pq::index_params index_params; + * // create and fill the index from a [N, D] dataset + * ivf_pq::index index; + * ivf_pq::build(handle, index_params, dataset, index); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset raft::device_matrix_view to a row-major matrix [n_rows, dim] + * @param[out] idx reference to ivf_pq::index + * + */ +void build(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::index_params& index_params, + raft::device_matrix_view dataset, + cuvs::neighbors::ivf_pq::index* idx); + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_pq::index_params index_params; + * // create and fill the index from a [N, D] dataset + * auto index = ivf_pq::build(handle, index_params, dataset); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset a device matrix view to a row-major matrix [n_rows, dim] + * + * @return the constructed ivf-pq index + */ +auto build(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::index_params& index_params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::ivf_pq::index; + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_pq::index_params index_params; + * // create and fill the index from a [N, D] dataset + * ivf_pq::index index; + * ivf_pq::build(handle, index_params, dataset, index); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset raft::device_matrix_view to a row-major matrix [n_rows, dim] + * @param[out] idx reference to ivf_pq::index + * + */ +void build(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::index_params& index_params, + raft::device_matrix_view dataset, + cuvs::neighbors::ivf_pq::index* idx); + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_pq::index_params index_params; + * // create and fill the index from a [N, D] dataset + * auto index = ivf_pq::build(handle, index_params, dataset); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset a device matrix view to a row-major matrix [n_rows, dim] + * + * @return the constructed ivf-pq index + */ +auto build(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::index_params& index_params, + raft::device_matrix_view dataset) + -> cuvs::neighbors::ivf_pq::index; + +/** + * @brief Build the index from the dataset for efficient search. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * // use default index parameters + * ivf_pq::index_params index_params; + * // create and fill the index from a [N, D] dataset + * ivf_pq::index index; + * ivf_pq::build(handle, index_params, dataset, index); + * @endcode + * + * @param[in] handle + * @param[in] index_params configure the index building + * @param[in] dataset raft::device_matrix_view to a row-major matrix [n_rows, dim] + * @param[out] idx reference to ivf_pq::index + * + */ +void build(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::index_params& index_params, + raft::device_matrix_view dataset, + cuvs::neighbors::ivf_pq::index* idx); +/** + * @} + */ + +/** + * @defgroup ivf_pq_cpp_index_extend IVF-PQ index extend + * @{ + */ +/** + * @brief Extend the index with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_pq::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_pq::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * auto index = ivf_pq::extend(handle, new_vectors, no_op, index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors a device matrix view to a row-major matrix [n_rows, idx.dim()] + * @param[in] new_indices a device vector view to a vector of indices [n_rows]. + * If the original index is empty (`idx.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx + */ +auto extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + const cuvs::neighbors::ivf_pq::index& idx) + -> cuvs::neighbors::ivf_pq::index; + +/** + * @brief Extend the index with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_pq::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_pq::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * ivf_pq::extend(handle, new_vectors, no_op, &index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors a device matrix view to a row-major matrix [n_rows, idx.dim()] + * @param[in] new_indices a device vector view to a vector of indices [n_rows]. + * If the original index is empty (`idx.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx + */ +void extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + cuvs::neighbors::ivf_pq::index* idx); + +/** + * @brief Extend the index with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_pq::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_pq::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * auto index = ivf_pq::extend(handle, new_vectors, no_op, index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors a device matrix view to a row-major matrix [n_rows, idx.dim()] + * @param[in] new_indices a device vector view to a vector of indices [n_rows]. + * If the original index is empty (`idx.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx + */ +auto extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + const cuvs::neighbors::ivf_pq::index& idx) + -> cuvs::neighbors::ivf_pq::index; + +/** + * @brief Extend the index with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_pq::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_pq::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * ivf_pq::extend(handle, new_vectors, no_op, &index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors a device matrix view to a row-major matrix [n_rows, idx.dim()] + * @param[in] new_indices a device vector view to a vector of indices [n_rows]. + * If the original index is empty (`idx.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx + */ +void extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + cuvs::neighbors::ivf_pq::index* idx); + +/** + * @brief Extend the index with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_pq::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_pq::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * auto index = ivf_pq::extend(handle, new_vectors, no_op, index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors a device matrix view to a row-major matrix [n_rows, idx.dim()] + * @param[in] new_indices a device vector view to a vector of indices [n_rows]. + * If the original index is empty (`idx.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx + */ +auto extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + const cuvs::neighbors::ivf_pq::index& idx) + -> cuvs::neighbors::ivf_pq::index; + +/** + * @brief Extend the index with the new data. + * + * Usage example: + * @code{.cpp} + * using namespace cuvs::neighbors; + * ivf_pq::index_params index_params; + * index_params.add_data_on_build = false; // don't populate index on build + * index_params.kmeans_trainset_fraction = 1.0; // use whole dataset for kmeans training + * // train the index from a [N, D] dataset + * auto index_empty = ivf_pq::build(handle, index_params, dataset); + * // fill the index with the data + * std::optional> no_op = std::nullopt; + * ivf_pq::extend(handle, new_vectors, no_op, &index_empty); + * @endcode + * + * @param[in] handle + * @param[in] new_vectors a device matrix view to a row-major matrix [n_rows, idx.dim()] + * @param[in] new_indices a device vector view to a vector of indices [n_rows]. + * If the original index is empty (`idx.size() == 0`), you can pass `std::nullopt` + * here to imply a continuous range `[0...n_rows)`. + * @param[inout] idx + */ +void extend(raft::resources const& handle, + raft::device_matrix_view new_vectors, + std::optional> new_indices, + cuvs::neighbors::ivf_pq::index* idx); +/** + * @} + */ + +/** + * @defgroup ivf_pq_cpp_index_search IVF-PQ index search + * @{ + */ +/** + * @brief Search ANN using the constructed index. + * + * See the [ivf_pq::build](#ivf_pq::build) documentation for a usage example. + * + * Note, this function requires a temporary buffer to store intermediate results between cuda kernel + * calls, which may lead to undesirable allocations and slowdown. To alleviate the problem, you can + * pass a pool memory resource or a large enough pre-allocated memory resource to reduce or + * eliminate entirely allocations happening within `search`. + * The exact size of the temporary buffer depends on multiple factors and is an implementation + * detail. However, you can safely specify a small initial size for the memory pool, so that only a + * few allocations happen to grow it during the first invocations of the `search`. + * + * @code{.cpp} + * ... + * // use default search parameters + * ivf_pq::search_params search_params; + * // Use the same allocator across multiple searches to reduce the number of + * // cuda memory allocations + * ivf_pq::search(handle, search_params, index, queries1, out_inds1, out_dists1); + * ivf_pq::search(handle, search_params, index, queries2, out_inds2, out_dists2); + * ivf_pq::search(handle, search_params, index, queries3, out_inds3, out_dists3); + * ... + * @endcode + * + * @param[in] handle + * @param[in] search_params configure the search + * @param[in] index ivf-pq constructed index + * @param[in] queries a device matrix view to a row-major matrix [n_queries, index->dim()] + * @param[out] neighbors a device matrix view to the indices of the neighbors in the source dataset + * [n_queries, k] + * @param[out] distances a device matrix view to the distances to the selected neighbors [n_queries, + * k] + */ +void search(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::search_params& search_params, + cuvs::neighbors::ivf_pq::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); + +/** + * @brief Search ANN using the constructed index. + * + * See the [ivf_pq::build](#ivf_pq::build) documentation for a usage example. + * + * Note, this function requires a temporary buffer to store intermediate results between cuda kernel + * calls, which may lead to undesirable allocations and slowdown. To alleviate the problem, you can + * pass a pool memory resource or a large enough pre-allocated memory resource to reduce or + * eliminate entirely allocations happening within `search`. + * The exact size of the temporary buffer depends on multiple factors and is an implementation + * detail. However, you can safely specify a small initial size for the memory pool, so that only a + * few allocations happen to grow it during the first invocations of the `search`. + * + * @code{.cpp} + * ... + * // use default search parameters + * ivf_pq::search_params search_params; + * // Use the same allocator across multiple searches to reduce the number of + * // cuda memory allocations + * ivf_pq::search(handle, search_params, index, queries1, out_inds1, out_dists1); + * ivf_pq::search(handle, search_params, index, queries2, out_inds2, out_dists2); + * ivf_pq::search(handle, search_params, index, queries3, out_inds3, out_dists3); + * ... + * @endcode + * + * @param[in] handle + * @param[in] search_params configure the search + * @param[in] index ivf-pq constructed index + * @param[in] queries a device matrix view to a row-major matrix [n_queries, index->dim()] + * @param[out] neighbors a device matrix view to the indices of the neighbors in the source dataset + * [n_queries, k] + * @param[out] distances a device matrix view to the distances to the selected neighbors [n_queries, + * k] + */ +void search(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::search_params& search_params, + cuvs::neighbors::ivf_pq::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); + +/** + * @brief Search ANN using the constructed index. + * + * See the [ivf_pq::build](#ivf_pq::build) documentation for a usage example. + * + * Note, this function requires a temporary buffer to store intermediate results between cuda kernel + * calls, which may lead to undesirable allocations and slowdown. To alleviate the problem, you can + * pass a pool memory resource or a large enough pre-allocated memory resource to reduce or + * eliminate entirely allocations happening within `search`. + * The exact size of the temporary buffer depends on multiple factors and is an implementation + * detail. However, you can safely specify a small initial size for the memory pool, so that only a + * few allocations happen to grow it during the first invocations of the `search`. + * + * @code{.cpp} + * ... + * // use default search parameters + * ivf_pq::search_params search_params; + * // Use the same allocator across multiple searches to reduce the number of + * // cuda memory allocations + * ivf_pq::search(handle, search_params, index, queries1, out_inds1, out_dists1); + * ivf_pq::search(handle, search_params, index, queries2, out_inds2, out_dists2); + * ivf_pq::search(handle, search_params, index, queries3, out_inds3, out_dists3); + * ... + * @endcode + * + * @param[in] handle + * @param[in] search_params configure the search + * @param[in] index ivf-pq constructed index + * @param[in] queries a device matrix view to a row-major matrix [n_queries, index->dim()] + * @param[out] neighbors a device matrix view to the indices of the neighbors in the source dataset + * [n_queries, k] + * @param[out] distances a device matrix view to the distances to the selected neighbors [n_queries, + * k] + */ +void search(raft::resources const& handle, + const cuvs::neighbors::ivf_pq::search_params& search_params, + cuvs::neighbors::ivf_pq::index& index, + raft::device_matrix_view queries, + raft::device_matrix_view neighbors, + raft::device_matrix_view distances); +/** + * @} + */ + +/** + * @defgroup ivf_pq_cpp_serialize IVF-PQ index serialize + * @{ + */ +/** + * Save the index to file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * // create an index with `auto index = ivf_pq::build(...);` + * cuvs::serialize(handle, filename, index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the file name for saving the index + * @param[in] index IVF-PQ index + * + */ +void serialize(raft::resources const& handle, + std::string& filename, + const cuvs::neighbors::ivf_pq::index& index); + +/** + * Load index from file. + * + * Experimental, both the API and the serialization format are subject to change. + * + * @code{.cpp} + * #include + * + * raft::resources handle; + * + * // create a string with a filepath + * std::string filename("/path/to/index"); + * using IdxT = int64_t; // type of the index + * // create an empty index with `ivf_pq::index index(handle, index_params, dim);` + * cuvs::deserialize(handle, filename, &index); + * @endcode + * + * @param[in] handle the raft handle + * @param[in] filename the name of the file that stores the index + * @param[out] index IVF-PQ index + * + */ +void deserialize(raft::resources const& handle, + const std::string& filename, + cuvs::neighbors::ivf_pq::index* index); +/** + * @} + */ + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/brute_force.cu b/cpp/src/neighbors/brute_force.cu new file mode 100644 index 0000000000..33dc2088ca --- /dev/null +++ b/cpp/src/neighbors/brute_force.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +namespace cuvs::neighbors::brute_force { + +#define CUVS_INST_BFKNN(T, IdxT) \ + auto build(raft::resources const& res, \ + raft::device_matrix_view dataset, \ + cuvs::distance::DistanceType metric, \ + T metric_arg) \ + ->cuvs::neighbors::brute_force::index \ + { \ + auto index_on_stack = raft::neighbors::brute_force::build( \ + res, dataset, static_cast(metric), metric_arg); \ + auto index_on_heap = \ + new raft::neighbors::brute_force::index(std::move(index_on_stack)); \ + return cuvs::neighbors::brute_force::index(index_on_heap); \ + } \ + \ + void search(raft::resources const& res, \ + const cuvs::neighbors::brute_force::index& idx, \ + raft::device_matrix_view queries, \ + raft::device_matrix_view neighbors, \ + raft::device_matrix_view distances) \ + { \ + auto raft_idx = \ + reinterpret_cast*>(idx.get_raft_index()); \ + raft::neighbors::brute_force::search(res, *raft_idx, queries, neighbors, distances); \ + } \ + \ + template struct cuvs::neighbors::brute_force::index; + +CUVS_INST_BFKNN(float, int64_t); +// CUVS_INST_BFKNN(int8_t, int64_t); +// CUVS_INST_BFKNN(uint8_t, int64_t); + +#undef CUVS_INST_BFKNN + +} // namespace cuvs::neighbors::brute_force diff --git a/cpp/src/neighbors/brute_force_c.cpp b/cpp/src/neighbors/brute_force_c.cpp new file mode 100644 index 0000000000..531fc9a57d --- /dev/null +++ b/cpp/src/neighbors/brute_force_c.cpp @@ -0,0 +1,167 @@ + +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +template +void* _build(cuvsResources_t res, + DLManagedTensor* dataset_tensor, + enum DistanceType metric, + T metric_arg) +{ + auto res_ptr = reinterpret_cast(res); + + using mdspan_type = raft::device_matrix_view; + auto mds = cuvs::core::from_dlpack(dataset_tensor); + + auto index_on_stack = cuvs::neighbors::brute_force::build( + *res_ptr, mds, static_cast((int)metric), metric_arg); + auto index_on_heap = new cuvs::neighbors::brute_force::index(std::move(index_on_stack)); + + return index_on_heap; +} + +template +void _search(cuvsResources_t res, + bruteForceIndex index, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) +{ + auto res_ptr = reinterpret_cast(res); + auto index_ptr = reinterpret_cast*>(index.addr); + + using queries_mdspan_type = raft::device_matrix_view; + using neighbors_mdspan_type = raft::device_matrix_view; + using distances_mdspan_type = raft::device_matrix_view; + auto queries_mds = cuvs::core::from_dlpack(queries_tensor); + auto neighbors_mds = cuvs::core::from_dlpack(neighbors_tensor); + auto distances_mds = cuvs::core::from_dlpack(distances_tensor); + + cuvs::neighbors::brute_force::search( + *res_ptr, *index_ptr, queries_mds, neighbors_mds, distances_mds); +} + +} // namespace + +extern "C" cuvsError_t bruteForceIndexCreate(cuvsBruteForceIndex_t* index) +{ + try { + *index = new bruteForceIndex{}; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t bruteForceIndexDestroy(cuvsBruteForceIndex_t index_c_ptr) +{ + try { + auto index = *index_c_ptr; + + if (index.dtype.code == kDLFloat) { + auto index_ptr = reinterpret_cast*>(index.addr); + delete index_ptr; + } else if (index.dtype.code == kDLInt) { + auto index_ptr = reinterpret_cast*>(index.addr); + delete index_ptr; + } else if (index.dtype.code == kDLUInt) { + auto index_ptr = reinterpret_cast*>(index.addr); + delete index_ptr; + } + delete index_c_ptr; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t bruteForceBuild(cuvsResources_t res, + DLManagedTensor* dataset_tensor, + enum DistanceType metric, + float metric_arg, + cuvsBruteForceIndex_t index) +{ + try { + auto dataset = dataset_tensor->dl_tensor; + + if (dataset.dtype.code == kDLFloat && dataset.dtype.bits == 32) { + index->addr = + reinterpret_cast(_build(res, dataset_tensor, metric, metric_arg)); + index->dtype.code = kDLFloat; + } else { + RAFT_FAIL("Unsupported dataset DLtensor dtype: %d and bits: %d", + dataset.dtype.code, + dataset.dtype.bits); + } + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t bruteForceSearch(cuvsResources_t res, + cuvsBruteForceIndex_t index_c_ptr, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) +{ + try { + auto queries = queries_tensor->dl_tensor; + auto neighbors = neighbors_tensor->dl_tensor; + auto distances = distances_tensor->dl_tensor; + + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(queries), + "queries should have device compatible memory"); + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(neighbors), + "neighbors should have device compatible memory"); + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(distances), + "distances should have device compatible memory"); + + RAFT_EXPECTS(neighbors.dtype.code == kDLInt && neighbors.dtype.bits == 64, + "neighbors should be of type int64_t"); + RAFT_EXPECTS(distances.dtype.code == kDLFloat && distances.dtype.bits == 32, + "distances should be of type float32"); + + auto index = *index_c_ptr; + RAFT_EXPECTS(queries.dtype.code == index.dtype.code, "type mismatch between index and queries"); + + if (queries.dtype.code == kDLFloat && queries.dtype.bits == 32) { + _search(res, index, queries_tensor, neighbors_tensor, distances_tensor); + } else { + RAFT_FAIL("Unsupported queries DLtensor dtype: %d and bits: %d", + queries.dtype.code, + queries.dtype.bits); + } + + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} diff --git a/cpp/src/neighbors/brute_force_index.cu b/cpp/src/neighbors/brute_force_index.cu new file mode 100644 index 0000000000..b05fa7ced9 --- /dev/null +++ b/cpp/src/neighbors/brute_force_index.cu @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +namespace cuvs::neighbors::brute_force { + +template +inline const raft::neighbors::brute_force::index* get_underlying_index( + const cuvs::neighbors::brute_force::index* idx) +{ + return reinterpret_cast*>(idx->get_raft_index()); +} + +template +index::index(void* raft_index) + : cuvs::neighbors::ann::index(), raft_index_(reinterpret_cast(raft_index)) +{ +} + +template +cuvs::distance::DistanceType index::metric() const noexcept +{ + auto raft_index = cuvs::neighbors::brute_force::get_underlying_index(this); + return static_cast((int)raft_index->metric()); +} + +template +size_t index::size() const noexcept +{ + auto raft_index = get_underlying_index(this); + return raft_index->size(); +} + +template +size_t index::dim() const noexcept +{ + auto raft_index = get_underlying_index(this); + return raft_index->dim(); +} + +template +raft::device_matrix_view index::dataset() const noexcept +{ + auto raft_index = get_underlying_index(this); + return raft_index->dataset(); +} + +template +raft::device_vector_view index::norms() const +{ + auto raft_index = get_underlying_index(this); + return raft_index->norms(); +} + +template +bool index::has_norms() const noexcept +{ + auto raft_index = get_underlying_index(this); + return raft_index->has_norms(); +} + +template +T index::metric_arg() const noexcept +{ + auto raft_index = get_underlying_index(this); + return raft_index->metric_arg(); +} + +template struct index; + +} // namespace cuvs::neighbors::brute_force diff --git a/cpp/src/neighbors/cagra_c.cpp b/cpp/src/neighbors/cagra_c.cpp index 9fdfe2c1e7..9e50870163 100644 --- a/cpp/src/neighbors/cagra_c.cpp +++ b/cpp/src/neighbors/cagra_c.cpp @@ -162,14 +162,14 @@ extern "C" cuvsError_t cuvsCagraSearch(cuvsResources_t res, RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(queries), "queries should have device compatible memory"); RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(neighbors), - "queries should have device compatible memory"); + "neighbors should have device compatible memory"); RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(distances), - "queries should have device compatible memory"); + "distances should have device compatible memory"); RAFT_EXPECTS(neighbors.dtype.code == kDLUInt && neighbors.dtype.bits == 32, "neighbors should be of type uint32_t"); RAFT_EXPECTS(distances.dtype.code == kDLFloat && neighbors.dtype.bits == 32, - "neighbors should be of type float32"); + "distances should be of type float32"); auto index = *index_c_ptr; RAFT_EXPECTS(queries.dtype.code == index.dtype.code, "type mismatch between index and queries"); diff --git a/cpp/src/neighbors/ivf_flat/generate_ivf_flat.py b/cpp/src/neighbors/ivf_flat/generate_ivf_flat.py new file mode 100644 index 0000000000..bf0cad6d4a --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/generate_ivf_flat.py @@ -0,0 +1,174 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +header = """/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { +""" + +footer = """ +} // namespace cuvs::neighbors::ivf_flat +""" + +types = dict( + float_int64_t=("float", "int64_t"), + int8_t_int64_t=("int8_t", "int64_t"), + uint8_t_int64_t=("uint8_t", "int64_t"), +) + +build_macro = """ +#define CUVS_INST_IVF_FLAT_BUILD(T, IdxT) \\ + auto build(raft::resources const& handle, \\ + const cuvs::neighbors::ivf_flat::index_params& params, \\ + raft::device_matrix_view dataset) \\ + ->cuvs::neighbors::ivf_flat::index \\ + { \\ + return cuvs::neighbors::ivf_flat::index( \\ + std::move(raft::runtime::neighbors::ivf_flat::build(handle, params, dataset))); \\ + } \\ + \\ + void build(raft::resources const& handle, \\ + const cuvs::neighbors::ivf_flat::index_params& params, \\ + raft::device_matrix_view dataset, \\ + cuvs::neighbors::ivf_flat::index& idx) \\ + { \\ + raft::runtime::neighbors::ivf_flat::build(handle, params, dataset, *idx.get_raft_index()); \\ + } +""" + +extend_macro = """ +#define CUVS_INST_IVF_FLAT_EXTEND(T, IdxT) \\ + auto extend(raft::resources const& handle, \\ + raft::device_matrix_view new_vectors, \\ + std::optional> new_indices, \\ + const cuvs::neighbors::ivf_flat::index& orig_index) \\ + ->cuvs::neighbors::ivf_flat::index \\ + { \\ + return cuvs::neighbors::ivf_flat::index( \\ + std::move(raft::runtime::neighbors::ivf_flat::extend( \\ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \\ + } \\ + \\ + void extend(raft::resources const& handle, \\ + raft::device_matrix_view new_vectors, \\ + std::optional> new_indices, \\ + cuvs::neighbors::ivf_flat::index* idx) \\ + { \\ + raft::runtime::neighbors::ivf_flat::extend( \\ + handle, new_vectors, new_indices, idx->get_raft_index()); \\ + } +""" + +search_macro = """ +#define CUVS_INST_IVF_FLAT_SEARCH(T, IdxT) \\ + void search(raft::resources const& handle, \\ + const cuvs::neighbors::ivf_flat::search_params& params, \\ + cuvs::neighbors::ivf_flat::index& index, \\ + raft::device_matrix_view queries, \\ + raft::device_matrix_view neighbors, \\ + raft::device_matrix_view distances) \\ + { \\ + raft::runtime::neighbors::ivf_flat::search( \\ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \\ + } +""" + +serialize_macro = """ +#define CUVS_INST_IVF_FLAT_SERIALIZE(T, IdxT) \\ + void serialize_file(raft::resources const& handle, \\ + const std::string& filename, \\ + const cuvs::neighbors::ivf_flat::index& index) \\ + { \\ + raft::runtime::neighbors::ivf_flat::serialize_file(handle, filename, *index.get_raft_index()); \\ + } \\ + \\ + void deserialize_file(raft::resources const& handle, \\ + const std::string& filename, \\ + cuvs::neighbors::ivf_flat::index* index) \\ + { \\ + raft::runtime::neighbors::ivf_flat::deserialize_file( \\ + handle, filename, index->get_raft_index()); \\ + } \\ + \\ + void serialize(raft::resources const& handle, \\ + std::string& str, \\ + const cuvs::neighbors::ivf_flat::index& index) \\ + { \\ + raft::runtime::neighbors::ivf_flat::serialize(handle, str, *index.get_raft_index()); \\ + } \\ + \\ + void deserialize(raft::resources const& handle, \\ + const std::string& str, \\ + cuvs::neighbors::ivf_flat::index* index) \\ + { \\ + raft::runtime::neighbors::ivf_flat::deserialize(handle, str, index->get_raft_index()); \\ + } +""" + +macros = dict( + build=dict( + definition=build_macro, + name="CUVS_INST_IVF_FLAT_BUILD", + ), + extend=dict( + definition=extend_macro, + name="CUVS_INST_IVF_FLAT_EXTEND", + ), + search=dict( + definition=search_macro, + name="CUVS_INST_IVF_FLAT_SEARCH", + ), + serialize=dict( + definition=serialize_macro, + name="CUVS_INST_IVF_FLAT_SERIALIZE", + ), +) + +for type_path, (T, IdxT) in types.items(): + for macro_path, macro in macros.items(): + path = f"ivf_flat_{macro_path}_{type_path}.cpp" + with open(path, "w") as f: + f.write(header) + f.write(macro["definition"]) + f.write(f"{macro['name']}({T}, {IdxT});\n\n") + f.write(f"#undef {macro['name']}\n") + f.write(footer) + + print(f"src/neighbors/ivf_flat/{path}") diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_build_float_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_build_float_int64_t.cpp new file mode 100644 index 0000000000..177aaac11f --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_build_float_int64_t.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_BUILD(T, IdxT) \ + auto build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::index_params& params, \ + raft::device_matrix_view dataset) \ + ->cuvs::neighbors::ivf_flat::index \ + { \ + return cuvs::neighbors::ivf_flat::index( \ + std::move(raft::runtime::neighbors::ivf_flat::build(handle, params, dataset))); \ + } \ + \ + void build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::index_params& params, \ + raft::device_matrix_view dataset, \ + cuvs::neighbors::ivf_flat::index& idx) \ + { \ + raft::runtime::neighbors::ivf_flat::build(handle, params, dataset, *idx.get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_BUILD(float, int64_t); + +#undef CUVS_INST_IVF_FLAT_BUILD + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_build_int8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_build_int8_t_int64_t.cpp new file mode 100644 index 0000000000..6fe6e2b8d6 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_build_int8_t_int64_t.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_BUILD(T, IdxT) \ + auto build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::index_params& params, \ + raft::device_matrix_view dataset) \ + ->cuvs::neighbors::ivf_flat::index \ + { \ + return cuvs::neighbors::ivf_flat::index( \ + std::move(raft::runtime::neighbors::ivf_flat::build(handle, params, dataset))); \ + } \ + \ + void build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::index_params& params, \ + raft::device_matrix_view dataset, \ + cuvs::neighbors::ivf_flat::index& idx) \ + { \ + raft::runtime::neighbors::ivf_flat::build(handle, params, dataset, *idx.get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_BUILD(int8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_BUILD + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_build_uint8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_build_uint8_t_int64_t.cpp new file mode 100644 index 0000000000..01098ed450 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_build_uint8_t_int64_t.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_BUILD(T, IdxT) \ + auto build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::index_params& params, \ + raft::device_matrix_view dataset) \ + ->cuvs::neighbors::ivf_flat::index \ + { \ + return cuvs::neighbors::ivf_flat::index( \ + std::move(raft::runtime::neighbors::ivf_flat::build(handle, params, dataset))); \ + } \ + \ + void build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::index_params& params, \ + raft::device_matrix_view dataset, \ + cuvs::neighbors::ivf_flat::index& idx) \ + { \ + raft::runtime::neighbors::ivf_flat::build(handle, params, dataset, *idx.get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_BUILD(uint8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_BUILD + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_extend_float_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_extend_float_int64_t.cpp new file mode 100644 index 0000000000..04ca3a50ff --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_extend_float_int64_t.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_EXTEND(T, IdxT) \ + auto extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + const cuvs::neighbors::ivf_flat::index& orig_index) \ + ->cuvs::neighbors::ivf_flat::index \ + { \ + return cuvs::neighbors::ivf_flat::index( \ + std::move(raft::runtime::neighbors::ivf_flat::extend( \ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \ + } \ + \ + void extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + cuvs::neighbors::ivf_flat::index* idx) \ + { \ + raft::runtime::neighbors::ivf_flat::extend( \ + handle, new_vectors, new_indices, idx->get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_EXTEND(float, int64_t); + +#undef CUVS_INST_IVF_FLAT_EXTEND + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_extend_int8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_extend_int8_t_int64_t.cpp new file mode 100644 index 0000000000..accc53e04d --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_extend_int8_t_int64_t.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_EXTEND(T, IdxT) \ + auto extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + const cuvs::neighbors::ivf_flat::index& orig_index) \ + ->cuvs::neighbors::ivf_flat::index \ + { \ + return cuvs::neighbors::ivf_flat::index( \ + std::move(raft::runtime::neighbors::ivf_flat::extend( \ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \ + } \ + \ + void extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + cuvs::neighbors::ivf_flat::index* idx) \ + { \ + raft::runtime::neighbors::ivf_flat::extend( \ + handle, new_vectors, new_indices, idx->get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_EXTEND(int8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_EXTEND + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_extend_uint8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_extend_uint8_t_int64_t.cpp new file mode 100644 index 0000000000..e44ae51b12 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_extend_uint8_t_int64_t.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_EXTEND(T, IdxT) \ + auto extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + const cuvs::neighbors::ivf_flat::index& orig_index) \ + ->cuvs::neighbors::ivf_flat::index \ + { \ + return cuvs::neighbors::ivf_flat::index( \ + std::move(raft::runtime::neighbors::ivf_flat::extend( \ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \ + } \ + \ + void extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + cuvs::neighbors::ivf_flat::index* idx) \ + { \ + raft::runtime::neighbors::ivf_flat::extend( \ + handle, new_vectors, new_indices, idx->get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_EXTEND(uint8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_EXTEND + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_search_float_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_search_float_int64_t.cpp new file mode 100644 index 0000000000..48a584e9ec --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_search_float_int64_t.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_SEARCH(T, IdxT) \ + void search(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::search_params& params, \ + cuvs::neighbors::ivf_flat::index& index, \ + raft::device_matrix_view queries, \ + raft::device_matrix_view neighbors, \ + raft::device_matrix_view distances) \ + { \ + raft::runtime::neighbors::ivf_flat::search( \ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \ + } +CUVS_INST_IVF_FLAT_SEARCH(float, int64_t); + +#undef CUVS_INST_IVF_FLAT_SEARCH + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_search_int8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_search_int8_t_int64_t.cpp new file mode 100644 index 0000000000..5645c18e0f --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_search_int8_t_int64_t.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_SEARCH(T, IdxT) \ + void search(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::search_params& params, \ + cuvs::neighbors::ivf_flat::index& index, \ + raft::device_matrix_view queries, \ + raft::device_matrix_view neighbors, \ + raft::device_matrix_view distances) \ + { \ + raft::runtime::neighbors::ivf_flat::search( \ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \ + } +CUVS_INST_IVF_FLAT_SEARCH(int8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_SEARCH + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_search_uint8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_search_uint8_t_int64_t.cpp new file mode 100644 index 0000000000..ab1bee8b60 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_search_uint8_t_int64_t.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_SEARCH(T, IdxT) \ + void search(raft::resources const& handle, \ + const cuvs::neighbors::ivf_flat::search_params& params, \ + cuvs::neighbors::ivf_flat::index& index, \ + raft::device_matrix_view queries, \ + raft::device_matrix_view neighbors, \ + raft::device_matrix_view distances) \ + { \ + raft::runtime::neighbors::ivf_flat::search( \ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \ + } +CUVS_INST_IVF_FLAT_SEARCH(uint8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_SEARCH + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_float_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_float_int64_t.cpp new file mode 100644 index 0000000000..19a3d72d98 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_float_int64_t.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_SERIALIZE(T, IdxT) \ + void serialize_file(raft::resources const& handle, \ + const std::string& filename, \ + const cuvs::neighbors::ivf_flat::index& index) \ + { \ + raft::runtime::neighbors::ivf_flat::serialize_file(handle, filename, *index.get_raft_index()); \ + } \ + \ + void deserialize_file(raft::resources const& handle, \ + const std::string& filename, \ + cuvs::neighbors::ivf_flat::index* index) \ + { \ + raft::runtime::neighbors::ivf_flat::deserialize_file( \ + handle, filename, index->get_raft_index()); \ + } \ + \ + void serialize(raft::resources const& handle, \ + std::string& str, \ + const cuvs::neighbors::ivf_flat::index& index) \ + { \ + raft::runtime::neighbors::ivf_flat::serialize(handle, str, *index.get_raft_index()); \ + } \ + \ + void deserialize(raft::resources const& handle, \ + const std::string& str, \ + cuvs::neighbors::ivf_flat::index* index) \ + { \ + raft::runtime::neighbors::ivf_flat::deserialize(handle, str, index->get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_SERIALIZE(float, int64_t); + +#undef CUVS_INST_IVF_FLAT_SERIALIZE + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_int8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_int8_t_int64_t.cpp new file mode 100644 index 0000000000..f65fe221d4 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_int8_t_int64_t.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_SERIALIZE(T, IdxT) \ + void serialize_file(raft::resources const& handle, \ + const std::string& filename, \ + const cuvs::neighbors::ivf_flat::index& index) \ + { \ + raft::runtime::neighbors::ivf_flat::serialize_file(handle, filename, *index.get_raft_index()); \ + } \ + \ + void deserialize_file(raft::resources const& handle, \ + const std::string& filename, \ + cuvs::neighbors::ivf_flat::index* index) \ + { \ + raft::runtime::neighbors::ivf_flat::deserialize_file( \ + handle, filename, index->get_raft_index()); \ + } \ + \ + void serialize(raft::resources const& handle, \ + std::string& str, \ + const cuvs::neighbors::ivf_flat::index& index) \ + { \ + raft::runtime::neighbors::ivf_flat::serialize(handle, str, *index.get_raft_index()); \ + } \ + \ + void deserialize(raft::resources const& handle, \ + const std::string& str, \ + cuvs::neighbors::ivf_flat::index* index) \ + { \ + raft::runtime::neighbors::ivf_flat::deserialize(handle, str, index->get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_SERIALIZE(int8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_SERIALIZE + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_uint8_t_int64_t.cpp b/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_uint8_t_int64_t.cpp new file mode 100644 index 0000000000..5f312dc0a4 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat/ivf_flat_serialize_uint8_t_int64_t.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_flat.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_flat.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_flat { + +#define CUVS_INST_IVF_FLAT_SERIALIZE(T, IdxT) \ + void serialize_file(raft::resources const& handle, \ + const std::string& filename, \ + const cuvs::neighbors::ivf_flat::index& index) \ + { \ + raft::runtime::neighbors::ivf_flat::serialize_file(handle, filename, *index.get_raft_index()); \ + } \ + \ + void deserialize_file(raft::resources const& handle, \ + const std::string& filename, \ + cuvs::neighbors::ivf_flat::index* index) \ + { \ + raft::runtime::neighbors::ivf_flat::deserialize_file( \ + handle, filename, index->get_raft_index()); \ + } \ + \ + void serialize(raft::resources const& handle, \ + std::string& str, \ + const cuvs::neighbors::ivf_flat::index& index) \ + { \ + raft::runtime::neighbors::ivf_flat::serialize(handle, str, *index.get_raft_index()); \ + } \ + \ + void deserialize(raft::resources const& handle, \ + const std::string& str, \ + cuvs::neighbors::ivf_flat::index* index) \ + { \ + raft::runtime::neighbors::ivf_flat::deserialize(handle, str, index->get_raft_index()); \ + } +CUVS_INST_IVF_FLAT_SERIALIZE(uint8_t, int64_t); + +#undef CUVS_INST_IVF_FLAT_SERIALIZE + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_flat_c.cpp b/cpp/src/neighbors/ivf_flat_c.cpp new file mode 100644 index 0000000000..b9488ec366 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat_c.cpp @@ -0,0 +1,246 @@ + +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +template +void* _build(cuvsResources_t res, ivfFlatIndexParams params, DLManagedTensor* dataset_tensor) +{ + auto res_ptr = reinterpret_cast(res); + + auto build_params = cuvs::neighbors::ivf_flat::index_params(); + build_params.metric = static_cast((int)params.metric), + build_params.metric_arg = params.metric_arg; + build_params.add_data_on_build = params.add_data_on_build; + build_params.n_lists = params.n_lists; + build_params.kmeans_n_iters = params.kmeans_n_iters; + build_params.kmeans_trainset_fraction = params.kmeans_trainset_fraction; + build_params.adaptive_centers = params.adaptive_centers; + build_params.conservative_memory_allocation = params.conservative_memory_allocation; + + auto dataset = dataset_tensor->dl_tensor; + auto dim = dataset.shape[0]; + + auto index = new cuvs::neighbors::ivf_flat::index(*res_ptr, build_params, dim); + + using mdspan_type = raft::device_matrix_view; + auto mds = cuvs::core::from_dlpack(dataset_tensor); + + cuvs::neighbors::ivf_flat::build(*res_ptr, build_params, mds, *index); + + return index; +} + +template +void _search(cuvsResources_t res, + ivfFlatSearchParams params, + ivfFlatIndex index, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) +{ + auto res_ptr = reinterpret_cast(res); + auto index_ptr = reinterpret_cast*>(index.addr); + + auto search_params = cuvs::neighbors::ivf_flat::search_params(); + search_params.n_probes = params.n_probes; + + using queries_mdspan_type = raft::device_matrix_view; + using neighbors_mdspan_type = raft::device_matrix_view; + using distances_mdspan_type = raft::device_matrix_view; + auto queries_mds = cuvs::core::from_dlpack(queries_tensor); + auto neighbors_mds = cuvs::core::from_dlpack(neighbors_tensor); + auto distances_mds = cuvs::core::from_dlpack(distances_tensor); + + cuvs::neighbors::ivf_flat::search( + *res_ptr, search_params, *index_ptr, queries_mds, neighbors_mds, distances_mds); +} + +} // namespace + +extern "C" cuvsError_t ivfFlatIndexCreate(cuvsIvfFlatIndex_t* index) +{ + try { + *index = new ivfFlatIndex{}; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index_c_ptr) +{ + try { + auto index = *index_c_ptr; + + if (index.dtype.code == kDLFloat) { + auto index_ptr = + reinterpret_cast*>(index.addr); + delete index_ptr; + } else if (index.dtype.code == kDLInt) { + auto index_ptr = + reinterpret_cast*>(index.addr); + delete index_ptr; + } else if (index.dtype.code == kDLUInt) { + auto index_ptr = + reinterpret_cast*>(index.addr); + delete index_ptr; + } + delete index_c_ptr; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t ivfFlatBuild(cuvsResources_t res, + cuvsIvfFlatIndexParams_t params, + DLManagedTensor* dataset_tensor, + cuvsIvfFlatIndex_t index) +{ + try { + auto dataset = dataset_tensor->dl_tensor; + + if (dataset.dtype.code == kDLFloat && dataset.dtype.bits == 32) { + index->addr = + reinterpret_cast(_build(res, *params, dataset_tensor)); + index->dtype.code = kDLFloat; + } else if (dataset.dtype.code == kDLInt && dataset.dtype.bits == 8) { + index->addr = + reinterpret_cast(_build(res, *params, dataset_tensor)); + index->dtype.code = kDLInt; + } else if (dataset.dtype.code == kDLUInt && dataset.dtype.bits == 8) { + index->addr = + reinterpret_cast(_build(res, *params, dataset_tensor)); + index->dtype.code = kDLUInt; + } else { + RAFT_FAIL("Unsupported dataset DLtensor dtype: %d and bits: %d", + dataset.dtype.code, + dataset.dtype.bits); + } + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t ivfFlatSearch(cuvsResources_t res, + cuvsIvfFlatSearchParams_t params, + cuvsIvfFlatIndex_t index_c_ptr, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) +{ + try { + auto queries = queries_tensor->dl_tensor; + auto neighbors = neighbors_tensor->dl_tensor; + auto distances = distances_tensor->dl_tensor; + + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(queries), + "queries should have device compatible memory"); + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(neighbors), + "neighbors should have device compatible memory"); + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(distances), + "distances should have device compatible memory"); + + RAFT_EXPECTS(neighbors.dtype.code == kDLInt && neighbors.dtype.bits == 64, + "neighbors should be of type int64_t"); + RAFT_EXPECTS(distances.dtype.code == kDLFloat && distances.dtype.bits == 32, + "distances should be of type float32"); + + auto index = *index_c_ptr; + RAFT_EXPECTS(queries.dtype.code == index.dtype.code, "type mismatch between index and queries"); + + if (queries.dtype.code == kDLFloat && queries.dtype.bits == 32) { + _search( + res, *params, index, queries_tensor, neighbors_tensor, distances_tensor); + } else if (queries.dtype.code == kDLInt && queries.dtype.bits == 8) { + _search( + res, *params, index, queries_tensor, neighbors_tensor, distances_tensor); + } else if (queries.dtype.code == kDLUInt && queries.dtype.bits == 8) { + _search( + res, *params, index, queries_tensor, neighbors_tensor, distances_tensor); + } else { + RAFT_FAIL("Unsupported queries DLtensor dtype: %d and bits: %d", + queries.dtype.code, + queries.dtype.bits); + } + + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfFlatIndexParamsCreate(cuvsIvfFlatIndexParams_t* params) +{ + try { + *params = new ivfFlatIndexParams{.metric = L2Expanded, + .metric_arg = 2.0f, + .add_data_on_build = true, + .n_lists = 1024, + .kmeans_n_iters = 20, + .kmeans_trainset_fraction = 0.5, + .adaptive_centers = false, + .conservative_memory_allocation = false}; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfFlatIndexParamsDestroy(cuvsIvfFlatIndexParams_t params) +{ + try { + delete params; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfFlatSearchParamsCreate(cuvsIvfFlatSearchParams_t* params) +{ + try { + *params = new ivfFlatSearchParams{.n_probes = 20}; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfFlatSearchParamsDestroy(cuvsIvfFlatSearchParams_t params) +{ + try { + delete params; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} diff --git a/cpp/src/neighbors/ivf_flat_index.cpp b/cpp/src/neighbors/ivf_flat_index.cpp new file mode 100644 index 0000000000..678bec32a5 --- /dev/null +++ b/cpp/src/neighbors/ivf_flat_index.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace cuvs::neighbors::ivf_flat { + +template +index::index(raft::resources const& res, const index_params& params, uint32_t dim) + : ann::index(), + raft_index_(std::make_unique>( + res, + static_cast((int)params.metric), + params.n_lists, + params.adaptive_centers, + params.conservative_memory_allocation, + dim)) +{ +} + +template +index::index(raft::neighbors::ivf_flat::index&& raft_idx) + : ann::index(), + raft_index_(std::make_unique>(std::move(raft_idx))) +{ +} + +template +uint32_t index::veclen() const noexcept +{ + return raft_index_->veclen(); +} + +template +cuvs::distance::DistanceType index::metric() const noexcept +{ + return static_cast((int)raft_index_->metric()); +} + +template +bool index::adaptive_centers() const noexcept +{ + return raft_index_->adaptive_centers(); +} + +template +raft::device_vector_view index::list_sizes() noexcept +{ + return raft_index_->list_sizes(); +} + +template +raft::device_vector_view index::list_sizes() const noexcept +{ + return raft_index_->list_sizes(); +} + +template +raft::device_matrix_view index::centers() noexcept +{ + return raft_index_->centers(); +} + +template +raft::device_matrix_view index::centers() + const noexcept +{ + return raft_index_->centers(); +} + +template +std::optional> index::center_norms() noexcept +{ + return raft_index_->center_norms(); +} + +template +std::optional> index::center_norms() + const noexcept +{ + return raft_index_->center_norms(); +} + +template +IdxT index::size() const noexcept +{ + return raft_index_->size(); +} + +template +uint32_t index::dim() const noexcept +{ + return raft_index_->dim(); +} + +template +uint32_t index::n_lists() const noexcept +{ + return raft_index_->n_lists(); +} + +template +raft::device_vector_view index::data_ptrs() noexcept +{ + return raft_index_->data_ptrs(); +} + +template +raft::device_vector_view index::data_ptrs() const noexcept +{ + return raft_index_->data_ptrs(); +} + +template +raft::device_vector_view index::inds_ptrs() noexcept +{ + return raft_index_->inds_ptrs(); +} + +template +raft::device_vector_view index::inds_ptrs() const noexcept +{ + return raft_index_->inds_ptrs(); +} + +template +bool index::conservative_memory_allocation() const noexcept +{ + return raft_index_->conservative_memory_allocation(); +} + +template +std::vector>>& +index::lists() noexcept +{ + return raft_index_->lists(); +} + +template +const std::vector>>& +index::lists() const noexcept +{ + return raft_index_->lists(); +} + +template struct index; +template struct index; +template struct index; + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/src/neighbors/ivf_pq/generate_ivf_pq.py b/cpp/src/neighbors/ivf_pq/generate_ivf_pq.py new file mode 100644 index 0000000000..d0a5d2b192 --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/generate_ivf_pq.py @@ -0,0 +1,138 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +header = """/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { +""" + +footer = """ +} // namespace cuvs::neighbors::ivf_pq +""" + +types = dict( + float_int64_t=("float", "int64_t"), + int8_t_int64_t=("int8_t", "int64_t"), + uint8_t_int64_t=("uint8_t", "int64_t"), +) + +build_macro = """ +#define CUVS_INST_IVF_PQ_BUILD(T, IdxT) \\ + auto build(raft::resources const& handle, \\ + const cuvs::neighbors::ivf_pq::index_params& params, \\ + raft::device_matrix_view dataset) \\ + ->cuvs::neighbors::ivf_pq::index \\ + { \\ + return cuvs::neighbors::ivf_pq::index( \\ + std::move(raft::runtime::neighbors::ivf_pq::build(handle, params, dataset))); \\ + } \\ + \\ + void build(raft::resources const& handle, \\ + const cuvs::neighbors::ivf_pq::index_params& params, \\ + raft::device_matrix_view dataset, \\ + cuvs::neighbors::ivf_pq::index* idx) \\ + { \\ + raft::runtime::neighbors::ivf_pq::build(handle, params, dataset, idx->get_raft_index()); \\ + } +""" + +extend_macro = """ +#define CUVS_INST_IVF_PQ_EXTEND(T, IdxT) \\ + auto extend(raft::resources const& handle, \\ + raft::device_matrix_view new_vectors, \\ + std::optional> new_indices, \\ + const cuvs::neighbors::ivf_pq::index& orig_index) \\ + ->cuvs::neighbors::ivf_pq::index \\ + { \\ + return cuvs::neighbors::ivf_pq::index( \\ + std::move(raft::runtime::neighbors::ivf_pq::extend( \\ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \\ + } \\ + \\ + void extend(raft::resources const& handle, \\ + raft::device_matrix_view new_vectors, \\ + std::optional> new_indices, \\ + cuvs::neighbors::ivf_pq::index* idx) \\ + { \\ + raft::runtime::neighbors::ivf_pq::extend( \\ + handle, new_vectors, new_indices, idx->get_raft_index()); \\ + } +""" + +search_macro = """ +#define CUVS_INST_IVF_PQ_SEARCH(T, IdxT) \\ + void search(raft::resources const& handle, \\ + const cuvs::neighbors::ivf_pq::search_params& params, \\ + cuvs::neighbors::ivf_pq::index& index, \\ + raft::device_matrix_view queries, \\ + raft::device_matrix_view neighbors, \\ + raft::device_matrix_view distances) \\ + { \\ + raft::runtime::neighbors::ivf_pq::search( \\ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \\ + } +""" + +macros = dict( + build=dict( + definition=build_macro, + name="CUVS_INST_IVF_PQ_BUILD", + ), + extend=dict( + definition=extend_macro, + name="CUVS_INST_IVF_PQ_EXTEND", + ), + search=dict( + definition=search_macro, + name="CUVS_INST_IVF_PQ_SEARCH", + ), +) + +for type_path, (T, IdxT) in types.items(): + for macro_path, macro in macros.items(): + path = f"ivf_pq_{macro_path}_{type_path}.cpp" + with open(path, "w") as f: + f.write(header) + f.write(macro["definition"]) + f.write(f"{macro['name']}({T}, {IdxT});\n\n") + f.write(f"#undef {macro['name']}\n") + f.write(footer) + + print(f"src/neighbors/ivf_pq/{path}") diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_build_float_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_build_float_int64_t.cpp new file mode 100644 index 0000000000..78c4a0f67d --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_build_float_int64_t.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_BUILD(T, IdxT) \ + auto build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::index_params& params, \ + raft::device_matrix_view dataset) \ + ->cuvs::neighbors::ivf_pq::index \ + { \ + return cuvs::neighbors::ivf_pq::index( \ + std::move(raft::runtime::neighbors::ivf_pq::build(handle, params, dataset))); \ + } \ + \ + void build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::index_params& params, \ + raft::device_matrix_view dataset, \ + cuvs::neighbors::ivf_pq::index* idx) \ + { \ + raft::runtime::neighbors::ivf_pq::build(handle, params, dataset, idx->get_raft_index()); \ + } +CUVS_INST_IVF_PQ_BUILD(float, int64_t); + +#undef CUVS_INST_IVF_PQ_BUILD + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_build_int8_t_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_build_int8_t_int64_t.cpp new file mode 100644 index 0000000000..c9d7fc4c9c --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_build_int8_t_int64_t.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_BUILD(T, IdxT) \ + auto build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::index_params& params, \ + raft::device_matrix_view dataset) \ + ->cuvs::neighbors::ivf_pq::index \ + { \ + return cuvs::neighbors::ivf_pq::index( \ + std::move(raft::runtime::neighbors::ivf_pq::build(handle, params, dataset))); \ + } \ + \ + void build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::index_params& params, \ + raft::device_matrix_view dataset, \ + cuvs::neighbors::ivf_pq::index* idx) \ + { \ + raft::runtime::neighbors::ivf_pq::build(handle, params, dataset, idx->get_raft_index()); \ + } +CUVS_INST_IVF_PQ_BUILD(int8_t, int64_t); + +#undef CUVS_INST_IVF_PQ_BUILD + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp new file mode 100644 index 0000000000..24e56592df --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_BUILD(T, IdxT) \ + auto build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::index_params& params, \ + raft::device_matrix_view dataset) \ + ->cuvs::neighbors::ivf_pq::index \ + { \ + return cuvs::neighbors::ivf_pq::index( \ + std::move(raft::runtime::neighbors::ivf_pq::build(handle, params, dataset))); \ + } \ + \ + void build(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::index_params& params, \ + raft::device_matrix_view dataset, \ + cuvs::neighbors::ivf_pq::index* idx) \ + { \ + raft::runtime::neighbors::ivf_pq::build(handle, params, dataset, idx->get_raft_index()); \ + } +CUVS_INST_IVF_PQ_BUILD(uint8_t, int64_t); + +#undef CUVS_INST_IVF_PQ_BUILD + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_extend_float_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_extend_float_int64_t.cpp new file mode 100644 index 0000000000..ec189ca9a4 --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_extend_float_int64_t.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_EXTEND(T, IdxT) \ + auto extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + const cuvs::neighbors::ivf_pq::index& orig_index) \ + ->cuvs::neighbors::ivf_pq::index \ + { \ + return cuvs::neighbors::ivf_pq::index( \ + std::move(raft::runtime::neighbors::ivf_pq::extend( \ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \ + } \ + \ + void extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + cuvs::neighbors::ivf_pq::index* idx) \ + { \ + raft::runtime::neighbors::ivf_pq::extend( \ + handle, new_vectors, new_indices, idx->get_raft_index()); \ + } +CUVS_INST_IVF_PQ_EXTEND(float, int64_t); + +#undef CUVS_INST_IVF_PQ_EXTEND + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp new file mode 100644 index 0000000000..27eadec727 --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_EXTEND(T, IdxT) \ + auto extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + const cuvs::neighbors::ivf_pq::index& orig_index) \ + ->cuvs::neighbors::ivf_pq::index \ + { \ + return cuvs::neighbors::ivf_pq::index( \ + std::move(raft::runtime::neighbors::ivf_pq::extend( \ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \ + } \ + \ + void extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + cuvs::neighbors::ivf_pq::index* idx) \ + { \ + raft::runtime::neighbors::ivf_pq::extend( \ + handle, new_vectors, new_indices, idx->get_raft_index()); \ + } +CUVS_INST_IVF_PQ_EXTEND(int8_t, int64_t); + +#undef CUVS_INST_IVF_PQ_EXTEND + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp new file mode 100644 index 0000000000..072b30bb00 --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_EXTEND(T, IdxT) \ + auto extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + const cuvs::neighbors::ivf_pq::index& orig_index) \ + ->cuvs::neighbors::ivf_pq::index \ + { \ + return cuvs::neighbors::ivf_pq::index( \ + std::move(raft::runtime::neighbors::ivf_pq::extend( \ + handle, new_vectors, new_indices, *orig_index.get_raft_index()))); \ + } \ + \ + void extend(raft::resources const& handle, \ + raft::device_matrix_view new_vectors, \ + std::optional> new_indices, \ + cuvs::neighbors::ivf_pq::index* idx) \ + { \ + raft::runtime::neighbors::ivf_pq::extend( \ + handle, new_vectors, new_indices, idx->get_raft_index()); \ + } +CUVS_INST_IVF_PQ_EXTEND(uint8_t, int64_t); + +#undef CUVS_INST_IVF_PQ_EXTEND + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_search_float_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_search_float_int64_t.cpp new file mode 100644 index 0000000000..69db44d9be --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_search_float_int64_t.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_SEARCH(T, IdxT) \ + void search(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::search_params& params, \ + cuvs::neighbors::ivf_pq::index& index, \ + raft::device_matrix_view queries, \ + raft::device_matrix_view neighbors, \ + raft::device_matrix_view distances) \ + { \ + raft::runtime::neighbors::ivf_pq::search( \ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \ + } +CUVS_INST_IVF_PQ_SEARCH(float, int64_t); + +#undef CUVS_INST_IVF_PQ_SEARCH + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_search_int8_t_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_search_int8_t_int64_t.cpp new file mode 100644 index 0000000000..e2e96b9a78 --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_search_int8_t_int64_t.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_SEARCH(T, IdxT) \ + void search(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::search_params& params, \ + cuvs::neighbors::ivf_pq::index& index, \ + raft::device_matrix_view queries, \ + raft::device_matrix_view neighbors, \ + raft::device_matrix_view distances) \ + { \ + raft::runtime::neighbors::ivf_pq::search( \ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \ + } +CUVS_INST_IVF_PQ_SEARCH(int8_t, int64_t); + +#undef CUVS_INST_IVF_PQ_SEARCH + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq/ivf_pq_search_uint8_t_int64_t.cpp b/cpp/src/neighbors/ivf_pq/ivf_pq_search_uint8_t_int64_t.cpp new file mode 100644 index 0000000000..18e7f46183 --- /dev/null +++ b/cpp/src/neighbors/ivf_pq/ivf_pq_search_uint8_t_int64_t.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by generate_ivf_pq.py + * + * Make changes there and run in this directory: + * + * > python generate_ivf_pq.py + * + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_SEARCH(T, IdxT) \ + void search(raft::resources const& handle, \ + const cuvs::neighbors::ivf_pq::search_params& params, \ + cuvs::neighbors::ivf_pq::index& index, \ + raft::device_matrix_view queries, \ + raft::device_matrix_view neighbors, \ + raft::device_matrix_view distances) \ + { \ + raft::runtime::neighbors::ivf_pq::search( \ + handle, params, *index.get_raft_index(), queries, neighbors, distances); \ + } +CUVS_INST_IVF_PQ_SEARCH(uint8_t, int64_t); + +#undef CUVS_INST_IVF_PQ_SEARCH + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq_c.cpp b/cpp/src/neighbors/ivf_pq_c.cpp new file mode 100644 index 0000000000..27076cbc1e --- /dev/null +++ b/cpp/src/neighbors/ivf_pq_c.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +template +void* _build(cuvsResources_t res, ivfPqIndexParams params, DLManagedTensor* dataset_tensor) +{ + auto res_ptr = reinterpret_cast(res); + + auto build_params = cuvs::neighbors::ivf_pq::index_params(); + build_params.metric = static_cast((int)params.metric), + build_params.metric_arg = params.metric_arg; + build_params.add_data_on_build = params.add_data_on_build; + build_params.n_lists = params.n_lists; + build_params.kmeans_n_iters = params.kmeans_n_iters; + build_params.kmeans_trainset_fraction = params.kmeans_trainset_fraction; + build_params.pq_bits = params.pq_bits; + build_params.pq_dim = params.pq_dim; + build_params.codebook_kind = + static_cast((int)params.codebook_kind); + build_params.force_random_rotation = params.force_random_rotation; + build_params.conservative_memory_allocation = params.conservative_memory_allocation; + + auto dataset = dataset_tensor->dl_tensor; + auto dim = dataset.shape[0]; + + auto index = new cuvs::neighbors::ivf_pq::index(*res_ptr, build_params, dim); + + using mdspan_type = raft::device_matrix_view; + auto mds = cuvs::core::from_dlpack(dataset_tensor); + + cuvs::neighbors::ivf_pq::build(*res_ptr, build_params, mds, index); + + return index; +} + +template +void _search(cuvsResources_t res, + ivfPqSearchParams params, + ivfPqIndex index, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) +{ + auto res_ptr = reinterpret_cast(res); + auto index_ptr = reinterpret_cast*>(index.addr); + + auto search_params = cuvs::neighbors::ivf_pq::search_params(); + search_params.n_probes = params.n_probes; + search_params.lut_dtype = params.lut_dtype; + search_params.internal_distance_dtype = params.internal_distance_dtype; + search_params.preferred_shmem_carveout = params.preferred_shmem_carveout; + + using queries_mdspan_type = raft::device_matrix_view; + using neighbors_mdspan_type = raft::device_matrix_view; + using distances_mdspan_type = raft::device_matrix_view; + auto queries_mds = cuvs::core::from_dlpack(queries_tensor); + auto neighbors_mds = cuvs::core::from_dlpack(neighbors_tensor); + auto distances_mds = cuvs::core::from_dlpack(distances_tensor); + + cuvs::neighbors::ivf_pq::search( + *res_ptr, search_params, *index_ptr, queries_mds, neighbors_mds, distances_mds); +} + +} // namespace + +extern "C" cuvsError_t ivfPqIndexCreate(cuvsIvfPqIndex_t* index) +{ + try { + *index = new ivfPqIndex{}; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t ivfPqIndexDestroy(cuvsIvfPqIndex_t index_c_ptr) +{ + try { + auto index = *index_c_ptr; + + auto index_ptr = reinterpret_cast*>(index.addr); + delete index_ptr; + delete index_c_ptr; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t ivfPqBuild(cuvsResources_t res, + cuvsIvfPqIndexParams_t params, + DLManagedTensor* dataset_tensor, + cuvsIvfPqIndex_t index) +{ + try { + auto dataset = dataset_tensor->dl_tensor; + + if ((dataset.dtype.code == kDLFloat && dataset.dtype.bits == 32) || + (dataset.dtype.code == kDLInt && dataset.dtype.bits == 8) || + (dataset.dtype.code == kDLUInt && dataset.dtype.bits == 8)) { + index->addr = reinterpret_cast(_build(res, *params, dataset_tensor)); + index->dtype.code = dataset.dtype.code; + } else { + RAFT_FAIL("Unsupported dataset DLtensor dtype: %d and bits: %d", + dataset.dtype.code, + dataset.dtype.bits); + } + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t ivfPqSearch(cuvsResources_t res, + cuvsIvfPqSearchParams_t params, + cuvsIvfPqIndex_t index_c_ptr, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) +{ + try { + auto queries = queries_tensor->dl_tensor; + auto neighbors = neighbors_tensor->dl_tensor; + auto distances = distances_tensor->dl_tensor; + + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(queries), + "queries should have device compatible memory"); + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(neighbors), + "neighbors should have device compatible memory"); + RAFT_EXPECTS(cuvs::core::is_dlpack_device_compatible(distances), + "distances should have device compatible memory"); + + RAFT_EXPECTS(neighbors.dtype.code == kDLInt && neighbors.dtype.bits == 64, + "neighbors should be of type int64_t"); + RAFT_EXPECTS(distances.dtype.code == kDLFloat && distances.dtype.bits == 32, + "distances should be of type float32"); + + auto index = *index_c_ptr; + RAFT_EXPECTS(queries.dtype.code == index.dtype.code, "type mismatch between index and queries"); + + if ((queries.dtype.code == kDLFloat && queries.dtype.bits == 32) || + (queries.dtype.code == kDLInt && queries.dtype.bits == 8) || + (queries.dtype.code == kDLUInt && queries.dtype.bits == 8)) { + _search(res, *params, index, queries_tensor, neighbors_tensor, distances_tensor); + } else { + RAFT_FAIL("Unsupported queries DLtensor dtype: %d and bits: %d", + queries.dtype.code, + queries.dtype.bits); + } + + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfPqIndexParamsCreate(cuvsIvfPqIndexParams_t* params) +{ + try { + *params = new ivfPqIndexParams{.metric = L2Expanded, + .metric_arg = 2.0f, + .add_data_on_build = true, + .n_lists = 1024, + .kmeans_n_iters = 20, + .kmeans_trainset_fraction = 0.5, + .pq_bits = 8, + .pq_dim = 0, + .codebook_kind = codebook_gen::PER_SUBSPACE, + .force_random_rotation = false, + .conservative_memory_allocation = false}; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfPqIndexParamsDestroy(cuvsIvfPqIndexParams_t params) +{ + try { + delete params; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfPqSearchParamsCreate(cuvsIvfPqSearchParams_t* params) +{ + try { + *params = new ivfPqSearchParams{.n_probes = 20, + .lut_dtype = CUDA_R_32F, + .internal_distance_dtype = CUDA_R_32F, + .preferred_shmem_carveout = 1.0}; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} + +extern "C" cuvsError_t cuvsIvfPqSearchParamsDestroy(cuvsIvfPqSearchParams_t params) +{ + try { + delete params; + return CUVS_SUCCESS; + } catch (...) { + return CUVS_ERROR; + } +} diff --git a/cpp/src/neighbors/ivf_pq_index.cpp b/cpp/src/neighbors/ivf_pq_index.cpp new file mode 100644 index 0000000000..b464da6706 --- /dev/null +++ b/cpp/src/neighbors/ivf_pq_index.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace cuvs::neighbors::ivf_pq { + +template +index::index(raft::resources const& handle, const index_params& params, uint32_t dim) + : ann::index(), + raft_index_(std::make_unique>( + handle, + static_cast((int)params.metric), + static_cast((int)params.codebook_kind), + params.n_lists, + dim, + params.pq_bits, + params.pq_dim, + params.conservative_memory_allocation)) +{ +} + +template +index::index(raft::neighbors::ivf_pq::index&& raft_idx) + : ann::index(), + raft_index_(std::make_unique>(std::move(raft_idx))) +{ +} + +template +IdxT index::size() const noexcept +{ + return raft_index_->size(); +} + +template +uint32_t index::dim() const noexcept +{ + return raft_index_->dim(); +} + +template +uint32_t index::dim_ext() const noexcept +{ + return raft_index_->dim_ext(); +} + +template +uint32_t index::rot_dim() const noexcept +{ + return raft_index_->rot_dim(); +} + +template +uint32_t index::pq_bits() const noexcept +{ + return raft_index_->pq_bits(); +} + +template +uint32_t index::pq_dim() const noexcept +{ + return raft_index_->pq_dim(); +} + +template +uint32_t index::pq_len() const noexcept +{ + return raft_index_->pq_len(); +} + +template +uint32_t index::pq_book_size() const noexcept +{ + return raft_index_->pq_book_size(); +} + +template +cuvs::distance::DistanceType index::metric() const noexcept +{ + return static_cast((int)raft_index_->metric()); +} + +template +codebook_gen index::codebook_kind() const noexcept +{ + return static_cast((int)raft_index_->codebook_kind()); +} + +template +uint32_t index::n_lists() const noexcept +{ + return raft_index_->n_lists(); +} + +template +bool index::conservative_memory_allocation() const noexcept +{ + return raft_index_->conservative_memory_allocation(); +} + +template +raft:: + mdspan::pq_centers_extents, raft::row_major> + index::pq_centers() noexcept +{ + return raft_index_->pq_centers(); +} + +template +raft::mdspan::pq_centers_extents, + raft::row_major> +index::pq_centers() const noexcept +{ + return raft_index_->pq_centers(); +} + +template +std::vector>>& index::lists() noexcept +{ + return raft_index_->lists(); +} + +template +const std::vector>>& index::lists() const noexcept +{ + return raft_index_->lists(); +} + +template +raft::device_vector_view index::data_ptrs() noexcept +{ + return raft_index_->data_ptrs(); +} + +template +raft::device_vector_view index::data_ptrs() + const noexcept +{ + return raft_index_->data_ptrs(); +} + +template +raft::device_vector_view index::inds_ptrs() noexcept +{ + return raft_index_->inds_ptrs(); +} + +template +raft::device_vector_view index::inds_ptrs() + const noexcept +{ + return raft_index_->inds_ptrs(); +} + +template +raft::device_matrix_view index::rotation_matrix() noexcept +{ + return raft_index_->rotation_matrix(); +} + +template +raft::device_matrix_view index::rotation_matrix() + const noexcept +{ + return raft_index_->rotation_matrix(); +} + +template +raft::host_vector_view index::accum_sorted_sizes() noexcept +{ + return raft_index_->accum_sorted_sizes(); +} + +template +raft::host_vector_view index::accum_sorted_sizes() + const noexcept +{ + return raft_index_->accum_sorted_sizes(); +} + +template +raft::device_vector_view index::list_sizes() noexcept +{ + return raft_index_->list_sizes(); +} + +template +raft::device_vector_view index::list_sizes() + const noexcept +{ + return raft_index_->list_sizes(); +} + +template +raft::device_matrix_view index::centers() noexcept +{ + return raft_index_->centers(); +} + +template +raft::device_matrix_view index::centers() + const noexcept +{ + return raft_index_->centers(); +} + +template +raft::device_matrix_view index::centers_rot() noexcept +{ + return raft_index_->centers_rot(); +} + +template +raft::device_matrix_view index::centers_rot() + const noexcept +{ + return raft_index_->centers_rot(); +} + +template struct index; + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/src/neighbors/ivf_pq_serialize.cpp b/cpp/src/neighbors/ivf_pq_serialize.cpp new file mode 100644 index 0000000000..4bdd3b04bc --- /dev/null +++ b/cpp/src/neighbors/ivf_pq_serialize.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +#define CUVS_INST_IVF_PQ_SERIALIZE(IdxT) \ + void serialize(raft::resources const& handle, \ + std::string& filename, \ + const cuvs::neighbors::ivf_pq::index& index) \ + { \ + raft::runtime::neighbors::ivf_pq::serialize(handle, filename, *index.get_raft_index()); \ + } \ + void deserialize(raft::resources const& handle, \ + const std::string& filename, \ + cuvs::neighbors::ivf_pq::index* index) \ + { \ + raft::runtime::neighbors::ivf_pq::deserialize(handle, filename, index->get_raft_index()); \ + } + +CUVS_INST_IVF_PQ_SERIALIZE(int64_t); + +#undef CUVS_INST_IVF_PQ_SERIALIZE + +} // namespace cuvs::neighbors::ivf_pq \ No newline at end of file diff --git a/cpp/test/CMakeLists.txt b/cpp/test/CMakeLists.txt index f33c141790..841058f60f 100644 --- a/cpp/test/CMakeLists.txt +++ b/cpp/test/CMakeLists.txt @@ -88,6 +88,34 @@ endfunction() # ################################################################################################## if(BUILD_TESTS) + ConfigureTest(NAME NEIGHBORS_TEST PATH test/neighbors/brute_force.cu GPUS 1 PERCENT 100) + + ConfigureTest( + NAME + NEIGHBORS_ANN_IVF_FLAT_TEST + PATH + test/neighbors/ann_ivf_flat/test_float_int64_t.cu + test/neighbors/ann_ivf_flat/test_int8_t_int64_t.cu + test/neighbors/ann_ivf_flat/test_uint8_t_int64_t.cu + GPUS + 1 + PERCENT + 100 + ) + + ConfigureTest( + NAME + NEIGHBORS_ANN_IVF_PQ_TEST + PATH + test/neighbors/ann_ivf_pq/test_float_int64_t.cu + test/neighbors/ann_ivf_pq/test_int8_t_int64_t.cu + test/neighbors/ann_ivf_pq/test_uint8_t_int64_t.cu + GPUS + 1 + PERCENT + 100 + ) + ConfigureTest( NAME NEIGHBORS_ANN_CAGRA_TEST @@ -105,6 +133,32 @@ endif() if(BUILD_C_TESTS) ConfigureTest(NAME INTEROP_TEST PATH test/core/interop.cu C_LIB) + ConfigureTest( + NAME + BRUTEFORCE_C_TEST + PATH + test/neighbors/run_brute_force_c.c + test/neighbors/brute_force_c.cu + C_LIB + ) + + ConfigureTest( + NAME + IVF_FLAT_C_TEST + PATH + test/neighbors/run_ivf_flat_c.c + test/neighbors/ann_ivf_flat_c.cu + C_LIB + ) + + ConfigureTest( + NAME + IVF_PQ_C_TEST + PATH + test/neighbors/run_ivf_pq_c.c + test/neighbors/ann_ivf_pq_c.cu C_LIB + ) + ConfigureTest(NAME CAGRA_C_TEST PATH test/neighbors/ann_cagra_c.cu C_LIB) endif() diff --git a/cpp/test/neighbors/ann_ivf_flat.cuh b/cpp/test/neighbors/ann_ivf_flat.cuh new file mode 100644 index 0000000000..90b70ba112 --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_flat.cuh @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "../test_utils.cuh" +#include "ann_utils.cuh" +#include "naive_knn.cuh" + +#include +#include +#include + +#include + +namespace cuvs::neighbors::ivf_flat { + +struct test_ivf_sample_filter { + static constexpr unsigned offset = 300; +}; + +template +struct AnnIvfFlatInputs { + IdxT num_queries; + IdxT num_db_vecs; + IdxT dim; + IdxT k; + IdxT nprobe; + IdxT nlist; + cuvs::distance::DistanceType metric; + bool adaptive_centers; + // bool host_dataset; +}; + +template +::std::ostream& operator<<(::std::ostream& os, const AnnIvfFlatInputs& p) +{ + os << "{ " << p.num_queries << ", " << p.num_db_vecs << ", " << p.dim << ", " << p.k << ", " + << p.nprobe << ", " << p.nlist << ", " << static_cast(p.metric) << ", " + << p.adaptive_centers << '}' << std::endl; + return os; +} + +template +class AnnIVFFlatTest : public ::testing::TestWithParam> { + public: + AnnIVFFlatTest() + : stream_(raft::resource::get_cuda_stream(handle_)), + ps(::testing::TestWithParam>::GetParam()), + database(0, stream_), + search_queries(0, stream_) + { + } + + void testIVFFlat() + { + size_t queries_size = ps.num_queries * ps.k; + std::vector indices_ivfflat(queries_size); + std::vector indices_naive(queries_size); + std::vector distances_ivfflat(queries_size); + std::vector distances_naive(queries_size); + + { + rmm::device_uvector distances_naive_dev(queries_size, stream_); + rmm::device_uvector indices_naive_dev(queries_size, stream_); + cuvs::neighbors::naive_knn(handle_, + distances_naive_dev.data(), + indices_naive_dev.data(), + search_queries.data(), + database.data(), + ps.num_queries, + ps.num_db_vecs, + ps.dim, + ps.k, + ps.metric); + raft::update_host(distances_naive.data(), distances_naive_dev.data(), queries_size, stream_); + raft::update_host(indices_naive.data(), indices_naive_dev.data(), queries_size, stream_); + raft::resource::sync_stream(handle_); + } + + { + // unless something is really wrong with clustering, this could serve as a lower bound on + // recall + double min_recall = static_cast(ps.nprobe) / static_cast(ps.nlist); + + rmm::device_uvector distances_ivfflat_dev(queries_size, stream_); + rmm::device_uvector indices_ivfflat_dev(queries_size, stream_); + + { + // legacy interface + raft::spatial::knn::IVFFlatParam ivfParams; + ivfParams.nprobe = ps.nprobe; + ivfParams.nlist = ps.nlist; + raft::spatial::knn::knnIndex index; + + raft::spatial::knn::approx_knn_build_index( + handle_, + &index, + dynamic_cast(&ivfParams), + static_cast((int)ps.metric), + (IdxT)0, + database.data(), + ps.num_db_vecs, + ps.dim); + + raft::resource::sync_stream(handle_); + raft::spatial::knn::approx_knn_search(handle_, + distances_ivfflat_dev.data(), + indices_ivfflat_dev.data(), + &index, + ps.k, + search_queries.data(), + ps.num_queries); + + raft::update_host( + distances_ivfflat.data(), distances_ivfflat_dev.data(), queries_size, stream_); + raft::update_host( + indices_ivfflat.data(), indices_ivfflat_dev.data(), queries_size, stream_); + raft::resource::sync_stream(handle_); + } + + ASSERT_TRUE(eval_neighbours(indices_naive, + indices_ivfflat, + distances_naive, + distances_ivfflat, + ps.num_queries, + ps.k, + 0.001, + min_recall)); + { + cuvs::neighbors::ivf_flat::index_params index_params; + cuvs::neighbors::ivf_flat::search_params search_params; + index_params.n_lists = ps.nlist; + index_params.metric = ps.metric; + index_params.adaptive_centers = ps.adaptive_centers; + search_params.n_probes = ps.nprobe; + + index_params.add_data_on_build = false; + index_params.kmeans_trainset_fraction = 0.5; + index_params.metric_arg = 0; + + cuvs::neighbors::ivf_flat::index idx(handle_, index_params, ps.dim); + cuvs::neighbors::ivf_flat::index index_2(handle_, index_params, ps.dim); + + // if (!ps.host_dataset) { + + auto database_view = raft::make_device_matrix_view( + (const DataT*)database.data(), ps.num_db_vecs, ps.dim); + idx = cuvs::neighbors::ivf_flat::build(handle_, index_params, database_view); + rmm::device_uvector vector_indices(ps.num_db_vecs, stream_); + thrust::sequence(raft::resource::get_thrust_policy(handle_), + thrust::device_pointer_cast(vector_indices.data()), + thrust::device_pointer_cast(vector_indices.data() + ps.num_db_vecs)); + raft::resource::sync_stream(handle_); + + IdxT half_of_data = ps.num_db_vecs / 2; + + auto half_of_data_view = raft::make_device_matrix_view( + (const DataT*)database.data(), half_of_data, ps.dim); + + const std::optional> no_opt = std::nullopt; + index_2 = cuvs::neighbors::ivf_flat::extend(handle_, half_of_data_view, no_opt, idx); + + auto new_half_of_data_view = raft::make_device_matrix_view( + database.data() + half_of_data * ps.dim, IdxT(ps.num_db_vecs) - half_of_data, ps.dim); + + auto new_half_of_data_indices_view = raft::make_device_vector_view( + vector_indices.data() + half_of_data, IdxT(ps.num_db_vecs) - half_of_data); + + cuvs::neighbors::ivf_flat::extend( + handle_, + new_half_of_data_view, + std::make_optional>( + new_half_of_data_indices_view), + &index_2); + + /* + } else { + auto host_database = raft::make_host_matrix(ps.num_db_vecs, ps.dim); + raft::copy( + host_database.data_handle(), database.data(), ps.num_db_vecs * ps.dim, stream_); + idx = ivf_flat::build(handle_, index_params, + raft::make_const_mdspan(host_database.view())); + + auto vector_indices = raft::make_host_vector(handle_, ps.num_db_vecs); + std::iota(vector_indices.data_handle(), vector_indices.data_handle() + ps.num_db_vecs, 0); + + IdxT half_of_data = ps.num_db_vecs / 2; + + auto half_of_data_view = raft::make_host_matrix_view( + (const DataT*)host_database.data_handle(), half_of_data, ps.dim); + + const std::optional> no_opt = std::nullopt; + index_2 = ivf_flat::extend(handle_, half_of_data_view, no_opt, idx); + + auto new_half_of_data_view = raft::make_host_matrix_view( + host_database.data_handle() + half_of_data * ps.dim, + IdxT(ps.num_db_vecs) - half_of_data, + ps.dim); + auto new_half_of_data_indices_view = raft::make_host_vector_view( + vector_indices.data_handle() + half_of_data, IdxT(ps.num_db_vecs) - half_of_data); + ivf_flat::extend(handle_, + new_half_of_data_view, + std::make_optional>( + new_half_of_data_indices_view), + &index_2); + } + */ + + auto search_queries_view = raft::make_device_matrix_view( + search_queries.data(), ps.num_queries, ps.dim); + auto indices_out_view = raft::make_device_matrix_view( + indices_ivfflat_dev.data(), ps.num_queries, ps.k); + auto dists_out_view = raft::make_device_matrix_view( + distances_ivfflat_dev.data(), ps.num_queries, ps.k); + const std::string filename = "ivf_flat_index"; + cuvs::neighbors::ivf_flat::serialize_file(handle_, filename, index_2); + cuvs::neighbors::ivf_flat::index index_loaded(handle_, index_params, ps.dim); + cuvs::neighbors::ivf_flat::deserialize_file(handle_, filename, &index_loaded); + ASSERT_EQ(index_2.size(), index_loaded.size()); + + cuvs::neighbors::ivf_flat::search(handle_, + search_params, + index_loaded, + search_queries_view, + indices_out_view, + dists_out_view); + + raft::update_host( + distances_ivfflat.data(), distances_ivfflat_dev.data(), queries_size, stream_); + raft::update_host( + indices_ivfflat.data(), indices_ivfflat_dev.data(), queries_size, stream_); + raft::resource::sync_stream(handle_); + + // Test the centroid invariants + if (index_2.adaptive_centers()) { + // The centers must be up-to-date with the corresponding data + std::vector list_sizes(index_2.n_lists()); + std::vector list_indices(index_2.n_lists()); + rmm::device_uvector centroid(ps.dim, stream_); + raft::copy( + list_sizes.data(), index_2.list_sizes().data_handle(), index_2.n_lists(), stream_); + raft::copy( + list_indices.data(), index_2.inds_ptrs().data_handle(), index_2.n_lists(), stream_); + raft::resource::sync_stream(handle_); + for (uint32_t l = 0; l < index_2.n_lists(); l++) { + if (list_sizes[l] == 0) continue; + rmm::device_uvector cluster_data(list_sizes[l] * ps.dim, stream_); + raft::spatial::knn::detail::utils::copy_selected((IdxT)list_sizes[l], + (IdxT)ps.dim, + database.data(), + list_indices[l], + (IdxT)ps.dim, + cluster_data.data(), + (IdxT)ps.dim, + stream_); + raft::stats::mean( + centroid.data(), cluster_data.data(), ps.dim, list_sizes[l], false, true, stream_); + ASSERT_TRUE(cuvs::devArrMatch(index_2.centers().data_handle() + ps.dim * l, + centroid.data(), + ps.dim, + cuvs::CompareApprox(0.001), + stream_)); + } + } else { + // The centers must be immutable + ASSERT_TRUE(cuvs::devArrMatch(index_2.centers().data_handle(), + idx.centers().data_handle(), + index_2.centers().size(), + cuvs::Compare(), + stream_)); + } + } + ASSERT_TRUE(eval_neighbours(indices_naive, + indices_ivfflat, + distances_naive, + distances_ivfflat, + ps.num_queries, + ps.k, + 0.001, + min_recall)); + } + } + + /* + void testPacker() + { + ivf_flat::index_params index_params; + ivf_flat::search_params search_params; + index_params.n_lists = ps.nlist; + index_params.metric = ps.metric; + index_params.adaptive_centers = false; + search_params.n_probes = ps.nprobe; + + index_params.add_data_on_build = false; + index_params.kmeans_trainset_fraction = 1.0; + index_params.metric_arg = 0; + + auto database_view = raft::make_device_matrix_view( + (const DataT*)database.data(), ps.num_db_vecs, ps.dim); + + auto idx = ivf_flat::build(handle_, index_params, database_view); + + const std::optional> no_opt = std::nullopt; + index extend_index = ivf_flat::extend(handle_, database_view, no_opt, idx); + + auto list_sizes = raft::make_host_vector(idx.n_lists()); + raft::update_host(list_sizes.data_handle(), + extend_index.list_sizes().data_handle(), + extend_index.n_lists(), + stream_); + raft::resource::sync_stream(handle_); + + auto& lists = idx.lists(); + + // conservative memory allocation for codepacking + auto list_device_spec = list_spec{idx.dim(), false}; + + for (uint32_t label = 0; label < idx.n_lists(); label++) { + uint32_t list_size = list_sizes.data_handle()[label]; + + ivf::resize_list(handle_, lists[label], list_device_spec, list_size, 0); + } + + idx.recompute_internal_state(handle_); + + using interleaved_group = Pow2; + + for (uint32_t label = 0; label < idx.n_lists(); label++) { + uint32_t list_size = list_sizes.data_handle()[label]; + + if (list_size > 0) { + uint32_t padded_list_size = interleaved_group::roundUp(list_size); + uint32_t n_elems = padded_list_size * idx.dim(); + auto list_data = lists[label]->data; + auto list_inds = extend_index.lists()[label]->indices; + + // fetch the flat codes + auto flat_codes = make_device_matrix(handle_, list_size, idx.dim()); + + matrix::gather( + handle_, + make_device_matrix_view( + (const DataT*)database.data(), static_cast(ps.num_db_vecs), idx.dim()), + make_device_vector_view((const IdxT*)list_inds.data_handle(), + list_size), + flat_codes.view()); + + helpers::codepacker::pack( + handle_, make_const_mdspan(flat_codes.view()), idx.veclen(), 0, list_data.view()); + + { + auto mask = make_device_vector(handle_, n_elems); + + linalg::map_offset(handle_, + mask.view(), + [dim = idx.dim(), + list_size, + padded_list_size, + chunk_size = util::FastIntDiv(idx.veclen())] __device__(auto i) { + uint32_t max_group_offset = + interleaved_group::roundDown(list_size); if (i < max_group_offset * dim) { return true; } + uint32_t surplus = (i - max_group_offset * dim); + uint32_t ingroup_id = interleaved_group::mod(surplus / chunk_size); + return ingroup_id < (list_size - max_group_offset); + }); + + // ensure that the correct number of indices are masked out + ASSERT_TRUE(thrust::reduce(raft::resource::get_thrust_policy(handle_), + mask.data_handle(), + mask.data_handle() + n_elems, + 0) == list_size * ps.dim); + + auto packed_list_data = make_device_vector(handle_, n_elems); + + linalg::map_offset(handle_, + packed_list_data.view(), + [mask = mask.data_handle(), + list_data = list_data.data_handle()] __device__(uint32_t i) { + if (mask[i]) return list_data[i]; + return DataT{0}; + }); + + auto extend_data = extend_index.lists()[label]->data; + auto extend_data_filtered = make_device_vector(handle_, n_elems); + linalg::map_offset(handle_, + extend_data_filtered.view(), + [mask = mask.data_handle(), + extend_data = extend_data.data_handle()] __device__(uint32_t i) { + if (mask[i]) return extend_data[i]; + return DataT{0}; + }); + + ASSERT_TRUE(cuvs::devArrMatch(packed_list_data.data_handle(), + extend_data_filtered.data_handle(), + n_elems, + cuvs::Compare(), + stream_)); + } + + auto unpacked_flat_codes = + make_device_matrix(handle_, list_size, idx.dim()); + + helpers::codepacker::unpack( + handle_, list_data.view(), idx.veclen(), 0, unpacked_flat_codes.view()); + + ASSERT_TRUE(cuvs::devArrMatch(flat_codes.data_handle(), + unpacked_flat_codes.data_handle(), + list_size * ps.dim, + cuvs::Compare(), + stream_)); + } + } + } + */ + + void testFilter() + { + size_t queries_size = ps.num_queries * ps.k; + std::vector indices_ivfflat(queries_size); + std::vector indices_naive(queries_size); + std::vector distances_ivfflat(queries_size); + std::vector distances_naive(queries_size); + + { + rmm::device_uvector distances_naive_dev(queries_size, stream_); + rmm::device_uvector indices_naive_dev(queries_size, stream_); + auto* database_filtered_ptr = database.data() + test_ivf_sample_filter::offset * ps.dim; + cuvs::neighbors::naive_knn(handle_, + distances_naive_dev.data(), + indices_naive_dev.data(), + search_queries.data(), + database_filtered_ptr, + ps.num_queries, + ps.num_db_vecs - test_ivf_sample_filter::offset, + ps.dim, + ps.k, + ps.metric); + raft::linalg::addScalar(indices_naive_dev.data(), + indices_naive_dev.data(), + IdxT(test_ivf_sample_filter::offset), + queries_size, + stream_); + raft::update_host(distances_naive.data(), distances_naive_dev.data(), queries_size, stream_); + raft::update_host(indices_naive.data(), indices_naive_dev.data(), queries_size, stream_); + raft::resource::sync_stream(handle_); + } + + /* + { + // unless something is really wrong with clustering, this could serve as a lower bound on + // recall + double min_recall = static_cast(ps.nprobe) / static_cast(ps.nlist); + + auto distances_ivfflat_dev = raft::make_device_matrix(handle_, ps.num_queries, ps.k); + auto indices_ivfflat_dev = + raft::make_device_matrix(handle_, ps.num_queries, ps.k); + + { + ivf_flat::index_params index_params; + ivf_flat::search_params search_params; + index_params.n_lists = ps.nlist; + index_params.metric = ps.metric; + index_params.adaptive_centers = ps.adaptive_centers; + search_params.n_probes = ps.nprobe; + + index_params.add_data_on_build = true; + index_params.kmeans_trainset_fraction = 0.5; + index_params.metric_arg = 0; + + // Create IVF Flat index + auto database_view = raft::make_device_matrix_view( + (const DataT*)database.data(), ps.num_db_vecs, ps.dim); + auto index = ivf_flat::build(handle_, index_params, database_view); + + // Create Bitset filter + auto removed_indices = + raft::make_device_vector(handle_, test_ivf_sample_filter::offset); + thrust::sequence(raft::resource::get_thrust_policy(handle_), + thrust::device_pointer_cast(removed_indices.data_handle()), + thrust::device_pointer_cast(removed_indices.data_handle() + + test_ivf_sample_filter::offset)); + raft::resource::sync_stream(handle_); + + raft::core::bitset removed_indices_bitset( + handle_, removed_indices.view(), ps.num_db_vecs); + + // Search with the filter + auto search_queries_view = raft::make_device_matrix_view( + search_queries.data(), ps.num_queries, ps.dim); + ivf_flat::search_with_filtering( + handle_, + search_params, + index, + search_queries_view, + indices_ivfflat_dev.view(), + distances_ivfflat_dev.view(), + raft::neighbors::filtering::bitset_filter(removed_indices_bitset.view())); + + raft::update_host( + distances_ivfflat.data(), distances_ivfflat_dev.data_handle(), queries_size, stream_); + raft::update_host( + indices_ivfflat.data(), indices_ivfflat_dev.data_handle(), queries_size, stream_); + raft::resource::sync_stream(handle_); + } + ASSERT_TRUE(eval_neighbours(indices_naive, + indices_ivfflat, + distances_naive, + distances_ivfflat, + ps.num_queries, + ps.k, + 0.001, + min_recall)); + } + */ + } + + void SetUp() override + { + database.resize(ps.num_db_vecs * ps.dim, stream_); + search_queries.resize(ps.num_queries * ps.dim, stream_); + + raft::random::RngState r(1234ULL); + if constexpr (std::is_same{}) { + raft::random::uniform( + handle_, r, database.data(), ps.num_db_vecs * ps.dim, DataT(0.1), DataT(2.0)); + raft::random::uniform( + handle_, r, search_queries.data(), ps.num_queries * ps.dim, DataT(0.1), DataT(2.0)); + } else { + raft::random::uniformInt( + handle_, r, database.data(), ps.num_db_vecs * ps.dim, DataT(1), DataT(20)); + raft::random::uniformInt( + handle_, r, search_queries.data(), ps.num_queries * ps.dim, DataT(1), DataT(20)); + } + raft::resource::sync_stream(handle_); + } + + void TearDown() override + { + raft::resource::sync_stream(handle_); + database.resize(0, stream_); + search_queries.resize(0, stream_); + } + + private: + raft::resources handle_; + rmm::cuda_stream_view stream_; + AnnIvfFlatInputs ps; + rmm::device_uvector database; + rmm::device_uvector search_queries; +}; + +const std::vector> inputs = { + // test various dims (aligned and not aligned to vector sizes) + {1000, 10000, 1, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, true}, + {1000, 10000, 2, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + {1000, 10000, 3, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, true}, + {1000, 10000, 4, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + {1000, 10000, 5, 16, 40, 1024, cuvs::distance::DistanceType::InnerProduct, false}, + {1000, 10000, 8, 16, 40, 1024, cuvs::distance::DistanceType::InnerProduct, true}, + {1000, 10000, 5, 16, 40, 1024, cuvs::distance::DistanceType::L2SqrtExpanded, false}, + {1000, 10000, 8, 16, 40, 1024, cuvs::distance::DistanceType::L2SqrtExpanded, true}, + + // test dims that do not fit into kernel shared memory limits + {1000, 10000, 2048, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + {1000, 10000, 2049, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + {1000, 10000, 2050, 16, 40, 1024, cuvs::distance::DistanceType::InnerProduct, false}, + {1000, 10000, 2051, 16, 40, 1024, cuvs::distance::DistanceType::InnerProduct, true}, + {1000, 10000, 2052, 16, 40, 1024, cuvs::distance::DistanceType::InnerProduct, false}, + {1000, 10000, 2053, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, true}, + {1000, 10000, 2056, 16, 40, 1024, cuvs::distance::DistanceType::L2Expanded, true}, + + // various random combinations + {1000, 10000, 16, 10, 40, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + {1000, 10000, 16, 10, 50, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + {1000, 10000, 16, 10, 70, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + {100, 10000, 16, 10, 20, 512, cuvs::distance::DistanceType::L2Expanded, false}, + {20, 100000, 16, 10, 20, 1024, cuvs::distance::DistanceType::L2Expanded, true}, + {1000, 100000, 16, 10, 20, 1024, cuvs::distance::DistanceType::L2Expanded, true}, + {10000, 131072, 8, 10, 20, 1024, cuvs::distance::DistanceType::L2Expanded, false}, + + /* + // host input data + {1000, 10000, 16, 10, 40, 1024, cuvs::distance::DistanceType::L2Expanded, false, true}, + {1000, 10000, 16, 10, 50, 1024, cuvs::distance::DistanceType::L2Expanded, false, true}, + {1000, 10000, 16, 10, 70, 1024, cuvs::distance::DistanceType::L2Expanded, false, true}, + {100, 10000, 16, 10, 20, 512, cuvs::distance::DistanceType::L2Expanded, false, true}, + {20, 100000, 16, 10, 20, 1024, cuvs::distance::DistanceType::L2Expanded, false, true}, + {1000, 100000, 16, 10, 20, 1024, cuvs::distance::DistanceType::L2Expanded, false, true}, + {10000, 131072, 8, 10, 20, 1024, cuvs::distance::DistanceType::L2Expanded, false, true}, + */ + + {1000, 10000, 16, 10, 40, 1024, cuvs::distance::DistanceType::InnerProduct, true}, + {1000, 10000, 16, 10, 50, 1024, cuvs::distance::DistanceType::InnerProduct, true}, + {1000, 10000, 16, 10, 70, 1024, cuvs::distance::DistanceType::InnerProduct, false}, + {100, 10000, 16, 10, 20, 512, cuvs::distance::DistanceType::InnerProduct, true}, + {20, 100000, 16, 10, 20, 1024, cuvs::distance::DistanceType::InnerProduct, true}, + {1000, 100000, 16, 10, 20, 1024, cuvs::distance::DistanceType::InnerProduct, false}, + {10000, 131072, 8, 10, 50, 1024, cuvs::distance::DistanceType::InnerProduct, true}, + + {1000, 10000, 4096, 20, 50, 1024, cuvs::distance::DistanceType::InnerProduct, false}, + + // test splitting the big query batches (> max gridDim.y) into smaller batches + {100000, 1024, 32, 10, 64, 64, cuvs::distance::DistanceType::InnerProduct, false}, + {1000000, 1024, 32, 10, 256, 256, cuvs::distance::DistanceType::InnerProduct, false}, + {98306, 1024, 32, 10, 64, 64, cuvs::distance::DistanceType::InnerProduct, true}, + + // test radix_sort for getting the cluster selection + {1000, + 10000, + 16, + 10, + raft::matrix::detail::select::warpsort::kMaxCapacity * 2, + raft::matrix::detail::select::warpsort::kMaxCapacity * 4, + cuvs::distance::DistanceType::L2Expanded, + false}, + {1000, + 10000, + 16, + 10, + raft::matrix::detail::select::warpsort::kMaxCapacity * 4, + raft::matrix::detail::select::warpsort::kMaxCapacity * 4, + cuvs::distance::DistanceType::InnerProduct, + false}, + + // The following two test cases should show very similar recall. + // num_queries, num_db_vecs, dim, k, nprobe, nlist, metric, adaptive_centers + {20000, 8712, 3, 10, 51, 66, cuvs::distance::DistanceType::L2Expanded, false}, + {100000, 8712, 3, 10, 51, 66, cuvs::distance::DistanceType::L2Expanded, false}}; + +} // namespace cuvs::neighbors::ivf_flat \ No newline at end of file diff --git a/cpp/test/neighbors/ann_ivf_flat/test_float_int64_t.cu b/cpp/test/neighbors/ann_ivf_flat/test_float_int64_t.cu new file mode 100644 index 0000000000..0ce168f5ef --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_flat/test_float_int64_t.cu @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "../ann_ivf_flat.cuh" + +namespace cuvs::neighbors::ivf_flat { + +typedef AnnIVFFlatTest AnnIVFFlatTestF_float; +TEST_P(AnnIVFFlatTestF_float, AnnIVFFlat) { this->testIVFFlat(); } + +INSTANTIATE_TEST_CASE_P(AnnIVFFlatTest, AnnIVFFlatTestF_float, ::testing::ValuesIn(inputs)); + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/test/neighbors/ann_ivf_flat/test_int8_t_int64_t.cu b/cpp/test/neighbors/ann_ivf_flat/test_int8_t_int64_t.cu new file mode 100644 index 0000000000..15935fd889 --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_flat/test_int8_t_int64_t.cu @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "../ann_ivf_flat.cuh" + +namespace cuvs::neighbors::ivf_flat { + +typedef AnnIVFFlatTest AnnIVFFlatTestF_int8; +TEST_P(AnnIVFFlatTestF_int8, AnnIVFFlat) { this->testIVFFlat(); } + +INSTANTIATE_TEST_CASE_P(AnnIVFFlatTest, AnnIVFFlatTestF_int8, ::testing::ValuesIn(inputs)); + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/test/neighbors/ann_ivf_flat/test_uint8_t_int64_t.cu b/cpp/test/neighbors/ann_ivf_flat/test_uint8_t_int64_t.cu new file mode 100644 index 0000000000..42a8dab2e1 --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_flat/test_uint8_t_int64_t.cu @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "../ann_ivf_flat.cuh" + +namespace cuvs::neighbors::ivf_flat { + +typedef AnnIVFFlatTest AnnIVFFlatTestF_uint8; +TEST_P(AnnIVFFlatTestF_uint8, AnnIVFFlat) { this->testIVFFlat(); } + +INSTANTIATE_TEST_CASE_P(AnnIVFFlatTest, AnnIVFFlatTestF_uint8, ::testing::ValuesIn(inputs)); + +} // namespace cuvs::neighbors::ivf_flat diff --git a/cpp/test/neighbors/ann_ivf_flat_c.cu b/cpp/test/neighbors/ann_ivf_flat_c.cu new file mode 100644 index 0000000000..e854504948 --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_flat_c.cu @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "ann_utils.cuh" +#include + +extern "C" void run_ivf_flat(int64_t n_rows, + int64_t n_queries, + int64_t n_dim, + uint32_t n_neighbors, + float* index_data, + float* query_data, + float* distances_data, + int64_t* neighbors_data, + enum DistanceType metric, + size_t n_probes, + size_t n_lists); + +template +void generate_random_data(T* devPtr, size_t size) +{ + raft::handle_t handle; + raft::random::RngState r(1234ULL); + raft::random::uniform(handle, r, devPtr, size, T(0.1), T(2.0)); +}; + +template +void recall_eval(T* query_data, + T* index_data, + IdxT* neighbors, + T* distances, + size_t n_queries, + size_t n_rows, + size_t n_dim, + size_t n_neighbors, + DistanceType metric, + size_t n_probes, + size_t n_lists) +{ + raft::handle_t handle; + auto distances_ref = raft::make_device_matrix(handle, n_queries, n_neighbors); + auto neighbors_ref = raft::make_device_matrix(handle, n_queries, n_neighbors); + cuvs::neighbors::naive_knn( + handle, + distances_ref.data_handle(), + neighbors_ref.data_handle(), + query_data, + index_data, + n_queries, + n_rows, + n_dim, + n_neighbors, + static_cast((uint16_t)metric)); + + size_t size = n_queries * n_neighbors; + std::vector neighbors_h(size); + std::vector distances_h(size); + std::vector neighbors_ref_h(size); + std::vector distances_ref_h(size); + + auto stream = raft::resource::get_cuda_stream(handle); + raft::copy(neighbors_h.data(), neighbors, size, stream); + raft::copy(distances_h.data(), distances, size, stream); + raft::copy(neighbors_ref_h.data(), neighbors_ref.data_handle(), size, stream); + raft::copy(distances_ref_h.data(), distances_ref.data_handle(), size, stream); + + // verify output + double min_recall = static_cast(n_probes) / static_cast(n_lists); + ASSERT_TRUE(cuvs::neighbors::eval_neighbours(neighbors_ref_h, + neighbors_h, + distances_ref_h, + distances_h, + n_queries, + n_neighbors, + 0.001, + min_recall)); +}; + +TEST(IvfFlatC, BuildSearch) +{ + int64_t n_rows = 8096; + int64_t n_queries = 128; + int64_t n_dim = 32; + uint32_t n_neighbors = 8; + + enum DistanceType metric = L2Expanded; + size_t n_probes = 20; + size_t n_lists = 1024; + + float *index_data, *query_data, *distances_data; + int64_t* neighbors_data; + cudaMalloc(&index_data, sizeof(float) * n_rows * n_dim); + cudaMalloc(&query_data, sizeof(float) * n_queries * n_dim); + cudaMalloc(&neighbors_data, sizeof(int64_t) * n_queries * n_neighbors); + cudaMalloc(&distances_data, sizeof(float) * n_queries * n_neighbors); + + generate_random_data(index_data, n_rows * n_dim); + generate_random_data(query_data, n_queries * n_dim); + + run_ivf_flat(n_rows, + n_queries, + n_dim, + n_neighbors, + index_data, + query_data, + distances_data, + neighbors_data, + metric, + n_probes, + n_lists); + + recall_eval(query_data, + index_data, + neighbors_data, + distances_data, + n_queries, + n_rows, + n_dim, + n_neighbors, + metric, + n_probes, + n_lists); + + // delete device memory + cudaFree(index_data); + cudaFree(query_data); + cudaFree(neighbors_data); + cudaFree(distances_data); +} diff --git a/cpp/test/neighbors/ann_ivf_pq.cuh b/cpp/test/neighbors/ann_ivf_pq.cuh new file mode 100644 index 0000000000..5e276cbf1c --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_pq.cuh @@ -0,0 +1,981 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "../test_utils.cuh" +#include "ann_utils.cuh" +#include "ivf_pq_helpers.cuh" +#include "naive_knn.cuh" +#include + +#include +#include +#include + +namespace cuvs::neighbors::ivf_pq { + +using namespace raft; + +struct test_ivf_sample_filter { + static constexpr unsigned offset = 300; +}; + +struct ivf_pq_inputs { + uint32_t num_db_vecs = 4096; + uint32_t num_queries = 1024; + uint32_t dim = 64; + uint32_t k = 32; + std::optional min_recall = std::nullopt; + + cuvs::neighbors::ivf_pq::index_params index_params; + cuvs::neighbors::ivf_pq::search_params search_params; + + // Set some default parameters for tests + ivf_pq_inputs() + { + index_params.n_lists = max(32u, min(1024u, num_db_vecs / 128u)); + index_params.kmeans_trainset_fraction = 1.0; + } +}; + +inline auto operator<<(std::ostream& os, const ivf_pq::codebook_gen& p) -> std::ostream& +{ + switch (p) { + case ivf_pq::codebook_gen::PER_CLUSTER: os << "codebook_gen::PER_CLUSTER"; break; + case ivf_pq::codebook_gen::PER_SUBSPACE: os << "codebook_gen::PER_SUBSPACE"; break; + default: RAFT_FAIL("unreachable code"); + } + return os; +} + +inline auto operator<<(std::ostream& os, const ivf_pq_inputs& p) -> std::ostream& +{ + ivf_pq_inputs dflt; + bool need_comma = false; +#define PRINT_DIFF_V(spec, val) \ + do { \ + if (dflt spec != p spec) { \ + if (need_comma) { os << ", "; } \ + os << #spec << " = " << val; \ + need_comma = true; \ + } \ + } while (0) +#define PRINT_DIFF(spec) PRINT_DIFF_V(spec, p spec) + + os << "ivf_pq_inputs {"; + PRINT_DIFF(.num_db_vecs); + PRINT_DIFF(.num_queries); + PRINT_DIFF(.dim); + PRINT_DIFF(.k); + PRINT_DIFF_V(.min_recall, p.min_recall.value_or(0)); + PRINT_DIFF_V(.index_params.metric, + cuvs::neighbors::print_metric{ + static_cast((int)p.index_params.metric)}); + PRINT_DIFF(.index_params.metric_arg); + PRINT_DIFF(.index_params.add_data_on_build); + PRINT_DIFF(.index_params.n_lists); + PRINT_DIFF(.index_params.kmeans_n_iters); + PRINT_DIFF(.index_params.kmeans_trainset_fraction); + PRINT_DIFF(.index_params.pq_bits); + PRINT_DIFF(.index_params.pq_dim); + PRINT_DIFF(.index_params.codebook_kind); + PRINT_DIFF(.index_params.force_random_rotation); + PRINT_DIFF(.search_params.n_probes); + PRINT_DIFF_V(.search_params.lut_dtype, cuvs::neighbors::print_dtype{p.search_params.lut_dtype}); + PRINT_DIFF_V(.search_params.internal_distance_dtype, + cuvs::neighbors::print_dtype{p.search_params.internal_distance_dtype}); + os << "}"; + return os; +} + +template +void compare_vectors_l2( + const raft::resources& res, T a, T b, uint32_t label, double compression_ratio, double eps) +{ + auto n_rows = a.extent(0); + auto dim = a.extent(1); + rmm::mr::managed_memory_resource managed_memory; + auto dist = make_device_mdarray(res, &managed_memory, make_extents(n_rows)); + linalg::map_offset(res, dist.view(), [a, b, dim] __device__(uint32_t i) { + spatial::knn::detail::utils::mapping f{}; + double d = 0.0f; + for (uint32_t j = 0; j < dim; j++) { + double t = f(a(i, j)) - f(b(i, j)); + d += t * t; + } + return sqrt(d / double(dim)); + }); + resource::sync_stream(res); + for (uint32_t i = 0; i < n_rows; i++) { + double d = dist(i); + // The theoretical estimate of the error is hard to come up with, + // the estimate below is based on experimentation + curse of dimensionality + ASSERT_LE(d, 1.2 * eps * std::pow(2.0, compression_ratio)) + << " (label = " << label << ", ix = " << i << ", eps = " << eps << ")"; + } +} + +template +auto min_output_size(const raft::resources& handle, + const ivf_pq::index& index, + uint32_t n_probes) -> IdxT +{ + auto acc_sizes = index.accum_sorted_sizes(); + uint32_t last_nonzero = index.n_lists(); + while (last_nonzero > 0 && acc_sizes(last_nonzero - 1) == acc_sizes(last_nonzero)) { + last_nonzero--; + } + return acc_sizes(last_nonzero) - acc_sizes(last_nonzero - std::min(last_nonzero, n_probes)); +} + +template +class ivf_pq_test : public ::testing::TestWithParam { + public: + ivf_pq_test() + : stream_(resource::get_cuda_stream(handle_)), + ps(::testing::TestWithParam::GetParam()), + database(0, stream_), + search_queries(0, stream_) + { + } + + void gen_data() + { + database.resize(size_t{ps.num_db_vecs} * size_t{ps.dim}, stream_); + search_queries.resize(size_t{ps.num_queries} * size_t{ps.dim}, stream_); + + raft::random::RngState r(1234ULL); + if constexpr (std::is_same{}) { + raft::random::uniform( + handle_, r, database.data(), ps.num_db_vecs * ps.dim, DataT(0.1), DataT(2.0)); + raft::random::uniform( + handle_, r, search_queries.data(), ps.num_queries * ps.dim, DataT(0.1), DataT(2.0)); + } else { + raft::random::uniformInt( + handle_, r, database.data(), ps.num_db_vecs * ps.dim, DataT(1), DataT(20)); + raft::random::uniformInt( + handle_, r, search_queries.data(), ps.num_queries * ps.dim, DataT(1), DataT(20)); + } + resource::sync_stream(handle_); + } + + void calc_ref() + { + size_t queries_size = size_t{ps.num_queries} * size_t{ps.k}; + rmm::device_uvector distances_naive_dev(queries_size, stream_); + rmm::device_uvector indices_naive_dev(queries_size, stream_); + cuvs::neighbors::naive_knn( + handle_, + distances_naive_dev.data(), + indices_naive_dev.data(), + search_queries.data(), + database.data(), + ps.num_queries, + ps.num_db_vecs, + ps.dim, + ps.k, + static_cast((int)ps.index_params.metric)); + distances_ref.resize(queries_size); + update_host(distances_ref.data(), distances_naive_dev.data(), queries_size, stream_); + indices_ref.resize(queries_size); + update_host(indices_ref.data(), indices_naive_dev.data(), queries_size, stream_); + resource::sync_stream(handle_); + } + + auto build_only() + { + auto ipams = ps.index_params; + ipams.add_data_on_build = true; + + auto index_view = + raft::make_device_matrix_view(database.data(), ps.num_db_vecs, ps.dim); + return cuvs::neighbors::ivf_pq::build(handle_, ipams, index_view); + } + + auto build_2_extends() + { + auto db_indices = make_device_vector(handle_, ps.num_db_vecs); + linalg::map_offset(handle_, db_indices.view(), identity_op{}); + resource::sync_stream(handle_); + auto size_1 = IdxT(ps.num_db_vecs) / 2; + auto size_2 = IdxT(ps.num_db_vecs) - size_1; + auto vecs_1 = database.data(); + auto vecs_2 = database.data() + size_t(size_1) * size_t(ps.dim); + auto inds_1 = db_indices.data_handle(); + auto inds_2 = db_indices.data_handle() + size_t(size_1); + + auto ipams = ps.index_params; + ipams.add_data_on_build = false; + + auto database_view = + raft::make_device_matrix_view(database.data(), ps.num_db_vecs, ps.dim); + auto idx = cuvs::neighbors::ivf_pq::build(handle_, ipams, database_view); + + auto vecs_2_view = raft::make_device_matrix_view(vecs_2, size_2, ps.dim); + auto inds_2_view = raft::make_device_vector_view(inds_2, size_2); + cuvs::neighbors::ivf_pq::extend(handle_, vecs_2_view, inds_2_view, &idx); + + auto vecs_1_view = + raft::make_device_matrix_view(vecs_1, size_1, ps.dim); + auto inds_1_view = raft::make_device_vector_view(inds_1, size_1); + cuvs::neighbors::ivf_pq::extend(handle_, vecs_1_view, inds_1_view, &idx); + return idx; + } + + auto build_serialize() + { + std::string filename = "ivf_pq_index"; + cuvs::neighbors::ivf_pq::serialize(handle_, filename, build_only()); + cuvs::neighbors::ivf_pq::index index(handle_, ps.index_params, ps.dim); + cuvs::neighbors::ivf_pq::deserialize(handle_, filename, &index); + return index; + } + + void check_reconstruction(const index& index, + double compression_ratio, + uint32_t label, + uint32_t n_take, + uint32_t n_skip) + { + auto& rec_list = index.lists()[label]; + auto dim = index.dim(); + n_take = std::min(n_take, rec_list->size.load()); + n_skip = std::min(n_skip, rec_list->size.load() - n_take); + + if (n_take == 0) { return; } + + auto rec_data = make_device_matrix(handle_, n_take, dim); + auto orig_data = make_device_matrix(handle_, n_take, dim); + + cuvs::neighbors::ivf_pq::helpers::reconstruct_list_data( + handle_, index, rec_data.view(), label, n_skip); + + matrix::gather(database.data(), + IdxT{dim}, + IdxT{n_take}, + rec_list->indices.data_handle() + n_skip, + IdxT{n_take}, + orig_data.data_handle(), + stream_); + + compare_vectors_l2(handle_, rec_data.view(), orig_data.view(), label, compression_ratio, 0.06); + } + + void check_reconstruct_extend(index* index, double compression_ratio, uint32_t label) + { + // NB: this is not reference, the list is retained; the index will have to create a new list on + // `erase_list` op. + auto old_list = index->lists()[label]; + auto n_rows = old_list->size.load(); + if (n_rows == 0) { return; } + + auto vectors_1 = make_device_matrix(handle_, n_rows, index->dim()); + auto indices = make_device_vector(handle_, n_rows); + copy(indices.data_handle(), old_list->indices.data_handle(), n_rows, stream_); + + cuvs::neighbors::ivf_pq::helpers::reconstruct_list_data( + handle_, *index, vectors_1.view(), label, 0); + cuvs::neighbors::ivf_pq::helpers::erase_list(handle_, index, label); + // NB: passing the type parameter because const->non-const implicit conversion of the mdspans + // breaks type inference + cuvs::neighbors::ivf_pq::helpers::extend_list( + handle_, index, vectors_1.view(), indices.view(), label); + + auto& new_list = index->lists()[label]; + ASSERT_NE(old_list.get(), new_list.get()) + << "The old list should have been shared and retained after ivf_pq index has erased the " + "corresponding cluster."; + + auto vectors_2 = make_device_matrix(handle_, n_rows, index->dim()); + cuvs::neighbors::ivf_pq::helpers::reconstruct_list_data( + handle_, *index, vectors_2.view(), label, 0); + // The code search is unstable, and there's high chance of repeating values of the lvl-2 codes. + // Hence, encoding-decoding chain often leads to altering both the PQ codes and the + // reconstructed data. + compare_vectors_l2( + handle_, vectors_1.view(), vectors_2.view(), label, compression_ratio, 0.04); // 0.025); + } + + void check_packing(index* index, uint32_t label) + { + auto old_list = index->lists()[label]; + auto n_rows = old_list->size.load(); + + if (n_rows == 0) { return; } + + auto codes = make_device_matrix(handle_, n_rows, index->pq_dim()); + auto indices = make_device_vector(handle_, n_rows); + copy(indices.data_handle(), old_list->indices.data_handle(), n_rows, stream_); + + cuvs::neighbors::ivf_pq::helpers::unpack_list_data(handle_, *index, codes.view(), label, 0); + cuvs::neighbors::ivf_pq::helpers::erase_list(handle_, index, label); + cuvs::neighbors::ivf_pq::helpers::extend_list_with_codes( + handle_, index, codes.view(), indices.view(), label); + + auto& new_list = index->lists()[label]; + ASSERT_NE(old_list.get(), new_list.get()) + << "The old list should have been shared and retained after ivf_pq index has erased the " + "corresponding cluster."; + auto list_data_size = (n_rows / raft::neighbors::ivf_pq::kIndexGroupSize) * + new_list->data.extent(1) * new_list->data.extent(2) * + new_list->data.extent(3); + + ASSERT_TRUE(old_list->data.size() >= list_data_size); + ASSERT_TRUE(new_list->data.size() >= list_data_size); + ASSERT_TRUE(cuvs::devArrMatch(old_list->data.data_handle(), + new_list->data.data_handle(), + list_data_size, + cuvs::Compare{})); + + // Pack a few vectors back to the list. + int row_offset = 9; + int n_vec = 3; + ASSERT_TRUE(row_offset + n_vec < n_rows); + size_t offset = row_offset * index->pq_dim(); + auto codes_to_pack = make_device_matrix_view( + codes.data_handle() + offset, n_vec, index->pq_dim()); + ivf_pq::helpers::pack_list_data(handle_, index, codes_to_pack, label, row_offset); + ASSERT_TRUE(cuvs::devArrMatch(old_list->data.data_handle(), + new_list->data.data_handle(), + list_data_size, + cuvs::Compare{})); + + // Another test with the API that take list_data directly + auto list_data = index->lists()[label]->data.view(); + uint32_t n_take = 4; + ASSERT_TRUE(row_offset + n_take < n_rows); + auto codes2 = raft::make_device_matrix(handle_, n_take, index->pq_dim()); + ivf_pq::helpers::codepacker::unpack( + handle_, list_data, index->pq_bits(), row_offset, codes2.view()); + + // Write it back + ivf_pq::helpers::codepacker::pack( + handle_, make_const_mdspan(codes2.view()), index->pq_bits(), row_offset, list_data); + ASSERT_TRUE(cuvs::devArrMatch(old_list->data.data_handle(), + new_list->data.data_handle(), + list_data_size, + cuvs::Compare{})); + } + + template + void run(BuildIndex build_index) + { + index index = build_index(); + + double compression_ratio = + static_cast(ps.dim * 8) / static_cast(index.pq_dim() * index.pq_bits()); + + for (uint32_t label = 0; label < index.n_lists(); label++) { + switch (label % 3) { + case 0: { + // Reconstruct and re-write vectors for one label + check_reconstruct_extend(&index, compression_ratio, label); + } break; + case 1: { + // Dump and re-write codes for one label + check_packing(&index, label); + } break; + default: { + // check a small subset of data in a randomly chosen cluster to see if the data + // reconstruction works well. + check_reconstruction(index, compression_ratio, label, 100, 7); + } + } + } + + size_t queries_size = ps.num_queries * ps.k; + std::vector indices_ivf_pq(queries_size); + std::vector distances_ivf_pq(queries_size); + + rmm::device_uvector distances_ivf_pq_dev(queries_size, stream_); + rmm::device_uvector indices_ivf_pq_dev(queries_size, stream_); + + auto query_view = + raft::make_device_matrix_view(search_queries.data(), ps.num_queries, ps.dim); + auto inds_view = raft::make_device_matrix_view( + indices_ivf_pq_dev.data(), ps.num_queries, ps.k); + auto dists_view = raft::make_device_matrix_view( + distances_ivf_pq_dev.data(), ps.num_queries, ps.k); + + cuvs::neighbors::ivf_pq::search( + handle_, ps.search_params, index, query_view, inds_view, dists_view); + + update_host(distances_ivf_pq.data(), distances_ivf_pq_dev.data(), queries_size, stream_); + update_host(indices_ivf_pq.data(), indices_ivf_pq_dev.data(), queries_size, stream_); + resource::sync_stream(handle_); + + // A very conservative lower bound on recall + double min_recall = + static_cast(ps.search_params.n_probes) / static_cast(ps.index_params.n_lists); + // Using a heuristic to lower the required recall due to code-packing errors + min_recall = + std::min(std::erfc(0.05 * compression_ratio / std::max(min_recall, 0.5)), min_recall); + // Use explicit per-test min recall value if provided. + min_recall = ps.min_recall.value_or(min_recall); + + ASSERT_TRUE(cuvs::neighbors::eval_neighbours(indices_ref, + indices_ivf_pq, + distances_ref, + distances_ivf_pq, + ps.num_queries, + ps.k, + 0.0001 * compression_ratio, + min_recall)) + << ps; + + // Test a few extra invariants + IdxT min_results = min_output_size(handle_, index, ps.search_params.n_probes); + IdxT max_oob = ps.k <= min_results ? 0 : ps.k - min_results; + IdxT found_oob = 0; + for (uint32_t query_ix = 0; query_ix < ps.num_queries; query_ix++) { + for (uint32_t k = 0; k < ps.k; k++) { + auto flat_i = query_ix * ps.k + k; + auto found_ix = indices_ivf_pq[flat_i]; + if (found_ix == raft::neighbors::ivf_pq::kOutOfBoundsRecord) { + found_oob++; + continue; + } + ASSERT_NE(found_ix, raft::neighbors::ivf::kInvalidRecord) + << "got an invalid record at query_ix = " << query_ix << ", k = " << k + << " (distance = " << distances_ivf_pq[flat_i] << ")"; + ASSERT_LT(found_ix, ps.num_db_vecs) + << "got an impossible index = " << found_ix << " at query_ix = " << query_ix + << ", k = " << k << " (distance = " << distances_ivf_pq[flat_i] << ")"; + } + } + ASSERT_LE(found_oob, max_oob) + << "got too many records out-of-bounds (see ivf_pq::kOutOfBoundsRecord)."; + if (found_oob > 0) { + RAFT_LOG_WARN( + "Got %zu results out-of-bounds because of large top-k (%zu) and small n_probes (%u) and " + "small DB size/n_lists ratio (%zu / %u)", + size_t(found_oob), + size_t(ps.k), + ps.search_params.n_probes, + size_t(ps.num_db_vecs), + ps.index_params.n_lists); + } + } + + void SetUp() override // NOLINT + { + gen_data(); + calc_ref(); + } + + void TearDown() override // NOLINT + { + cudaGetLastError(); + resource::sync_stream(handle_); + database.resize(0, stream_); + search_queries.resize(0, stream_); + } + + private: + raft::resources handle_; + rmm::cuda_stream_view stream_; + ivf_pq_inputs ps; // NOLINT + rmm::device_uvector database; // NOLINT + rmm::device_uvector search_queries; // NOLINT + std::vector indices_ref; // NOLINT + std::vector distances_ref; // NOLINT +}; + +template +class ivf_pq_filter_test : public ::testing::TestWithParam { + public: + ivf_pq_filter_test() + : stream_(resource::get_cuda_stream(handle_)), + ps(::testing::TestWithParam::GetParam()), + database(0, stream_), + search_queries(0, stream_) + { + } + + void gen_data() + { + database.resize(size_t{ps.num_db_vecs} * size_t{ps.dim}, stream_); + search_queries.resize(size_t{ps.num_queries} * size_t{ps.dim}, stream_); + + raft::random::RngState r(1234ULL); + if constexpr (std::is_same{}) { + raft::random::uniform( + handle_, r, database.data(), ps.num_db_vecs * ps.dim, DataT(0.1), DataT(2.0)); + raft::random::uniform( + handle_, r, search_queries.data(), ps.num_queries * ps.dim, DataT(0.1), DataT(2.0)); + } else { + raft::random::uniformInt( + handle_, r, database.data(), ps.num_db_vecs * ps.dim, DataT(1), DataT(20)); + raft::random::uniformInt( + handle_, r, search_queries.data(), ps.num_queries * ps.dim, DataT(1), DataT(20)); + } + resource::sync_stream(handle_); + } + + void calc_ref() + { + size_t queries_size = size_t{ps.num_queries} * size_t{ps.k}; + rmm::device_uvector distances_naive_dev(queries_size, stream_); + rmm::device_uvector indices_naive_dev(queries_size, stream_); + cuvs::neighbors::naive_knn( + handle_, + distances_naive_dev.data(), + indices_naive_dev.data(), + search_queries.data(), + database.data() + test_ivf_sample_filter::offset * ps.dim, + ps.num_queries, + ps.num_db_vecs - test_ivf_sample_filter::offset, + ps.dim, + ps.k, + static_cast((int)ps.index_params.metric)); + raft::linalg::addScalar(indices_naive_dev.data(), + indices_naive_dev.data(), + IdxT(test_ivf_sample_filter::offset), + queries_size, + stream_); + distances_ref.resize(queries_size); + update_host(distances_ref.data(), distances_naive_dev.data(), queries_size, stream_); + indices_ref.resize(queries_size); + update_host(indices_ref.data(), indices_naive_dev.data(), queries_size, stream_); + resource::sync_stream(handle_); + } + + auto build_only() + { + auto ipams = ps.index_params; + ipams.add_data_on_build = true; + + auto index_view = + raft::make_device_matrix_view(database.data(), ps.num_db_vecs, ps.dim); + return cuvs::neighbors::ivf_pq::build(handle_, ipams, index_view); + } + + template + void run(BuildIndex build_index) + { + index index = build_index(); + + double compression_ratio = + static_cast(ps.dim * 8) / static_cast(index.pq_dim() * index.pq_bits()); + size_t queries_size = ps.num_queries * ps.k; + std::vector indices_ivf_pq(queries_size); + std::vector distances_ivf_pq(queries_size); + + rmm::device_uvector distances_ivf_pq_dev(queries_size, stream_); + rmm::device_uvector indices_ivf_pq_dev(queries_size, stream_); + + auto query_view = + raft::make_device_matrix_view(search_queries.data(), ps.num_queries, ps.dim); + auto inds_view = raft::make_device_matrix_view( + indices_ivf_pq_dev.data(), ps.num_queries, ps.k); + auto dists_view = raft::make_device_matrix_view( + distances_ivf_pq_dev.data(), ps.num_queries, ps.k); + + // Create Bitset filter + auto removed_indices = + raft::make_device_vector(handle_, test_ivf_sample_filter::offset); + thrust::sequence( + resource::get_thrust_policy(handle_), + thrust::device_pointer_cast(removed_indices.data_handle()), + thrust::device_pointer_cast(removed_indices.data_handle() + test_ivf_sample_filter::offset)); + resource::sync_stream(handle_); + + raft::core::bitset removed_indices_bitset( + handle_, removed_indices.view(), ps.num_db_vecs); + raft::neighbors::ivf_pq::search_with_filtering( + handle_, + ps.search_params, + index, + query_view, + inds_view, + dists_view, + raft::neighbors::filtering::bitset_filter(removed_indices_bitset.view())); + + update_host(distances_ivf_pq.data(), distances_ivf_pq_dev.data(), queries_size, stream_); + update_host(indices_ivf_pq.data(), indices_ivf_pq_dev.data(), queries_size, stream_); + resource::sync_stream(handle_); + + // A very conservative lower bound on recall + double min_recall = + static_cast(ps.search_params.n_probes) / static_cast(ps.index_params.n_lists); + // Using a heuristic to lower the required recall due to code-packing errors + min_recall = + std::min(std::erfc(0.05 * compression_ratio / std::max(min_recall, 0.5)), min_recall); + // Use explicit per-test min recall value if provided. + min_recall = ps.min_recall.value_or(min_recall); + + ASSERT_TRUE(cuvs::neighbors::eval_neighbours(indices_ref, + indices_ivf_pq, + distances_ref, + distances_ivf_pq, + ps.num_queries, + ps.k, + 0.0001 * compression_ratio, + min_recall)) + << ps; + } + + void SetUp() override // NOLINT + { + gen_data(); + calc_ref(); + } + + void TearDown() override // NOLINT + { + cudaGetLastError(); + resource::sync_stream(handle_); + database.resize(0, stream_); + search_queries.resize(0, stream_); + } + + private: + raft::resources handle_; + rmm::cuda_stream_view stream_; + ivf_pq_inputs ps; // NOLINT + rmm::device_uvector database; // NOLINT + rmm::device_uvector search_queries; // NOLINT + std::vector indices_ref; // NOLINT + std::vector distances_ref; // NOLINT +}; + +/* Test cases */ +using test_cases_t = std::vector; + +// concatenate parameter sets for different type +template +auto operator+(const std::vector& a, const std::vector& b) -> std::vector +{ + std::vector res = a; + res.insert(res.end(), b.begin(), b.end()); + return res; +} + +inline auto defaults() -> test_cases_t { return {ivf_pq_inputs{}}; } + +template +auto map(const std::vector& xs, F f) -> std::vector +{ + std::vector ys(xs.size()); + std::transform(xs.begin(), xs.end(), ys.begin(), f); + return ys; +} + +inline auto with_dims(const std::vector& dims) -> test_cases_t +{ + return map(dims, [](uint32_t d) { + ivf_pq_inputs x; + x.dim = d; + return x; + }); +} + +/** These will surely trigger the fastest kernel available. */ +inline auto small_dims() -> test_cases_t { return with_dims({1, 2, 3, 4, 5, 8, 15, 16, 17}); } + +inline auto small_dims_per_cluster() -> test_cases_t +{ + return map(small_dims(), [](const ivf_pq_inputs& x) { + ivf_pq_inputs y(x); + y.index_params.codebook_kind = ivf_pq::codebook_gen::PER_CLUSTER; + return y; + }); +} + +inline auto big_dims() -> test_cases_t +{ + // with_dims({512, 513, 1023, 1024, 1025, 2048, 2049, 2050, 2053, 6144, 8192, 12288, 16384}); + auto xs = with_dims({512, 513, 1023, 1024, 1025, 2048, 2049, 2050, 2053, 6144}); + return map(xs, [](const ivf_pq_inputs& x) { + ivf_pq_inputs y(x); + uint32_t pq_len = 2; + y.index_params.pq_dim = div_rounding_up_safe(x.dim, pq_len); + // This comes from pure experimentation, also the recall depens a lot on pq_len. + y.min_recall = 0.48 + 0.028 * std::log2(x.dim); + return y; + }); +} + +/** These will surely trigger no-smem-lut kernel. */ +inline auto big_dims_moderate_lut() -> test_cases_t +{ + return map(big_dims(), [](const ivf_pq_inputs& x) { + ivf_pq_inputs y(x); + uint32_t pq_len = 2; + y.index_params.pq_dim = round_up_safe(div_rounding_up_safe(x.dim, pq_len), 4u); + y.index_params.pq_bits = 6; + y.search_params.lut_dtype = CUDA_R_16F; + y.min_recall = 0.69; + return y; + }); +} + +/** Some of these should trigger no-basediff kernel. */ +inline auto big_dims_small_lut() -> test_cases_t +{ + return map(big_dims(), [](const ivf_pq_inputs& x) { + ivf_pq_inputs y(x); + uint32_t pq_len = 8; + y.index_params.pq_dim = round_up_safe(div_rounding_up_safe(x.dim, pq_len), 4u); + y.index_params.pq_bits = 6; + y.search_params.lut_dtype = CUDA_R_8U; + y.min_recall = 0.21; + return y; + }); +} + +/** + * A minimal set of tests to check various enum-like parameters. + */ +inline auto enum_variety() -> test_cases_t +{ + test_cases_t xs; +#define ADD_CASE(f) \ + do { \ + xs.push_back({}); \ + ([](ivf_pq_inputs & x) f)(xs[xs.size() - 1]); \ + } while (0); + + ADD_CASE({ + x.index_params.codebook_kind = ivf_pq::codebook_gen::PER_CLUSTER; + x.min_recall = 0.86; + }); + ADD_CASE({ + x.index_params.codebook_kind = ivf_pq::codebook_gen::PER_SUBSPACE; + x.min_recall = 0.86; + }); + ADD_CASE({ + x.index_params.codebook_kind = ivf_pq::codebook_gen::PER_CLUSTER; + x.index_params.pq_bits = 4; + x.min_recall = 0.79; + }); + ADD_CASE({ + x.index_params.codebook_kind = ivf_pq::codebook_gen::PER_CLUSTER; + x.index_params.pq_bits = 5; + x.min_recall = 0.83; + }); + + ADD_CASE({ + x.index_params.pq_bits = 6; + x.min_recall = 0.84; + }); + ADD_CASE({ + x.index_params.pq_bits = 7; + x.min_recall = 0.85; + }); + ADD_CASE({ + x.index_params.pq_bits = 8; + x.min_recall = 0.86; + }); + + ADD_CASE({ + x.index_params.force_random_rotation = true; + x.min_recall = 0.86; + }); + ADD_CASE({ + x.index_params.force_random_rotation = false; + x.min_recall = 0.86; + }); + + ADD_CASE({ + x.search_params.lut_dtype = CUDA_R_32F; + x.min_recall = 0.86; + }); + ADD_CASE({ + x.search_params.lut_dtype = CUDA_R_16F; + x.min_recall = 0.86; + }); + ADD_CASE({ + x.search_params.lut_dtype = CUDA_R_8U; + x.min_recall = 0.84; + }); + + ADD_CASE({ + x.search_params.internal_distance_dtype = CUDA_R_32F; + x.min_recall = 0.86; + }); + ADD_CASE({ + x.search_params.internal_distance_dtype = CUDA_R_16F; + x.search_params.lut_dtype = CUDA_R_16F; + x.min_recall = 0.86; + }); + + return xs; +} + +inline auto enum_variety_l2() -> test_cases_t +{ + return map(enum_variety(), [](const ivf_pq_inputs& x) { + ivf_pq_inputs y(x); + y.index_params.metric = distance::DistanceType::L2Expanded; + return y; + }); +} + +inline auto enum_variety_ip() -> test_cases_t +{ + return map(enum_variety(), [](const ivf_pq_inputs& x) { + ivf_pq_inputs y(x); + if (y.min_recall.has_value()) { + if (y.search_params.lut_dtype == CUDA_R_8U) { + // InnerProduct score is signed, + // thus we're forced to used signed 8-bit representation, + // thus we have one bit less precision + y.min_recall = y.min_recall.value() * 0.90; + } else { + // In other cases it seems to perform a little bit better, still worse than L2 + y.min_recall = y.min_recall.value() * 0.94; + } + } + y.index_params.metric = distance::DistanceType::InnerProduct; + return y; + }); +} + +inline auto enum_variety_l2sqrt() -> test_cases_t +{ + return map(enum_variety(), [](const ivf_pq_inputs& x) { + ivf_pq_inputs y(x); + y.index_params.metric = distance::DistanceType::L2SqrtExpanded; + return y; + }); +} + +/** + * Try different number of n_probes, some of which may trigger the non-fused version of the search + * kernel. + */ +inline auto var_n_probes() -> test_cases_t +{ + ivf_pq_inputs dflt; + std::vector xs; + for (auto x = dflt.index_params.n_lists; x >= 1; x /= 2) { + xs.push_back(x); + } + return map(xs, [](uint32_t n_probes) { + ivf_pq_inputs x; + x.search_params.n_probes = n_probes; + return x; + }); +} + +/** + * Try different number of nearest neighbours. + * Values smaller than 32 test if the code behaves well when Capacity (== 32) does not change, + * but `k <= Capacity` changes. + * + * Values between `32 and ivf_pq::detail::kMaxCapacity` test various instantiations of the + * main kernel (Capacity-templated) + * + * Values above ivf_pq::detail::kMaxCapacity should trigger the non-fused version of the kernel + * (manage_local_topk = false). + * + * Also we test here various values that are close-but-not-power-of-two to catch any problems + * related to rounding/alignment. + * + * Note, we cannot control explicitly which instance of the search kernel to choose, hence it's + * important to try a variety of different values of `k` to make sure all paths are triggered. + * + * Set the log level to DEBUG (5) or above to inspect the selected kernel instances. + */ +inline auto var_k() -> test_cases_t +{ + return map( + {1, 2, 3, 5, 8, 15, 16, 32, 63, 65, 127, 128, 256, 257, 1023, 2048, 2049}, [](uint32_t k) { + ivf_pq_inputs x; + x.k = k; + // when there's not enough data, try more cluster probes + x.search_params.n_probes = max(x.search_params.n_probes, min(x.index_params.n_lists, k)); + return x; + }); +} + +/** + * Cases brought up from downstream projects. + */ +inline auto special_cases() -> test_cases_t +{ + test_cases_t xs; + +#define ADD_CASE(f) \ + do { \ + xs.push_back({}); \ + ([](ivf_pq_inputs & x) f)(xs[xs.size() - 1]); \ + } while (0); + + ADD_CASE({ + x.num_db_vecs = 1183514; + x.dim = 100; + x.num_queries = 10000; + x.k = 10; + x.index_params.codebook_kind = ivf_pq::codebook_gen::PER_SUBSPACE; + x.index_params.pq_dim = 10; + x.index_params.pq_bits = 8; + x.index_params.n_lists = 1024; + x.search_params.n_probes = 50; + }); + + ADD_CASE({ + x.num_db_vecs = 10000; + x.dim = 16; + x.num_queries = 500; + x.k = 128; + x.index_params.metric = distance::DistanceType::L2Expanded; + x.index_params.codebook_kind = ivf_pq::codebook_gen::PER_SUBSPACE; + x.index_params.pq_bits = 8; + x.index_params.n_lists = 100; + x.search_params.n_probes = 100; + }); + + ADD_CASE({ + x.num_db_vecs = 10000; + x.dim = 16; + x.num_queries = 500; + x.k = 129; + x.index_params.metric = distance::DistanceType::L2Expanded; + x.index_params.codebook_kind = ivf_pq::codebook_gen::PER_SUBSPACE; + x.index_params.pq_bits = 8; + x.index_params.n_lists = 100; + x.search_params.n_probes = 100; + }); + + return xs; +} + +/* Test instantiations */ + +#define TEST_BUILD_SEARCH(type) \ + TEST_P(type, build_search) /* NOLINT */ \ + { \ + this->run([this]() { return this->build_only(); }); \ + } + +#define TEST_BUILD_EXTEND_SEARCH(type) \ + TEST_P(type, build_extend_search) /* NOLINT */ \ + { \ + this->run([this]() { return this->build_2_extends(); }); \ + } + +#define TEST_BUILD_SERIALIZE_SEARCH(type) \ + TEST_P(type, build_serialize_search) /* NOLINT */ \ + { \ + this->run([this]() { return this->build_serialize(); }); \ + } + +#define INSTANTIATE(type, vals) \ + INSTANTIATE_TEST_SUITE_P(IvfPq, type, ::testing::ValuesIn(vals)); /* NOLINT */ + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/test/neighbors/ann_ivf_pq/test_float_int64_t.cu b/cpp/test/neighbors/ann_ivf_pq/test_float_int64_t.cu new file mode 100644 index 0000000000..5ca4cde68d --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_pq/test_float_int64_t.cu @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../ann_ivf_pq.cuh" + +namespace cuvs::neighbors::ivf_pq { + +using f32_f32_i64 = ivf_pq_test; + +TEST_BUILD_EXTEND_SEARCH(f32_f32_i64) +TEST_BUILD_SERIALIZE_SEARCH(f32_f32_i64) +INSTANTIATE(f32_f32_i64, defaults() + small_dims() + big_dims_moderate_lut()); + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/test/neighbors/ann_ivf_pq/test_int8_t_int64_t.cu b/cpp/test/neighbors/ann_ivf_pq/test_int8_t_int64_t.cu new file mode 100644 index 0000000000..0763b661e7 --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_pq/test_int8_t_int64_t.cu @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../ann_ivf_pq.cuh" + +namespace cuvs::neighbors::ivf_pq { + +using f32_i08_i64 = ivf_pq_test; + +TEST_BUILD_SEARCH(f32_i08_i64) +TEST_BUILD_SERIALIZE_SEARCH(f32_i08_i64) +INSTANTIATE(f32_i08_i64, defaults() + big_dims() + var_k()); + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/test/neighbors/ann_ivf_pq/test_uint8_t_int64_t.cu b/cpp/test/neighbors/ann_ivf_pq/test_uint8_t_int64_t.cu new file mode 100644 index 0000000000..93e7d5336b --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_pq/test_uint8_t_int64_t.cu @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../ann_ivf_pq.cuh" + +namespace cuvs::neighbors::ivf_pq { + +using f32_u08_i64 = ivf_pq_test; + +TEST_BUILD_SEARCH(f32_u08_i64) +TEST_BUILD_EXTEND_SEARCH(f32_u08_i64) +INSTANTIATE(f32_u08_i64, small_dims_per_cluster() + enum_variety()); + +} // namespace cuvs::neighbors::ivf_pq diff --git a/cpp/test/neighbors/ann_ivf_pq_c.cu b/cpp/test/neighbors/ann_ivf_pq_c.cu new file mode 100644 index 0000000000..94d121ce2f --- /dev/null +++ b/cpp/test/neighbors/ann_ivf_pq_c.cu @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "ann_utils.cuh" +#include + +extern "C" void run_ivf_pq(int64_t n_rows, + int64_t n_queries, + int64_t n_dim, + uint32_t n_neighbors, + float* index_data, + float* query_data, + float* distances_data, + int64_t* neighbors_data, + enum DistanceType metric, + size_t n_probes, + size_t n_lists); + +template +void generate_random_data(T* devPtr, size_t size) +{ + raft::handle_t handle; + raft::random::RngState r(1234ULL); + raft::random::uniform(handle, r, devPtr, size, T(0.1), T(2.0)); +}; + +template +void recall_eval(T* query_data, + T* index_data, + IdxT* neighbors, + T* distances, + size_t n_queries, + size_t n_rows, + size_t n_dim, + size_t n_neighbors, + DistanceType metric, + size_t n_probes, + size_t n_lists) +{ + raft::handle_t handle; + auto distances_ref = raft::make_device_matrix(handle, n_queries, n_neighbors); + auto neighbors_ref = raft::make_device_matrix(handle, n_queries, n_neighbors); + cuvs::neighbors::naive_knn( + handle, + distances_ref.data_handle(), + neighbors_ref.data_handle(), + query_data, + index_data, + n_queries, + n_rows, + n_dim, + n_neighbors, + static_cast((uint16_t)metric)); + + size_t size = n_queries * n_neighbors; + std::vector neighbors_h(size); + std::vector distances_h(size); + std::vector neighbors_ref_h(size); + std::vector distances_ref_h(size); + + auto stream = raft::resource::get_cuda_stream(handle); + raft::copy(neighbors_h.data(), neighbors, size, stream); + raft::copy(distances_h.data(), distances, size, stream); + raft::copy(neighbors_ref_h.data(), neighbors_ref.data_handle(), size, stream); + raft::copy(distances_ref_h.data(), distances_ref.data_handle(), size, stream); + + // verify output + double min_recall = static_cast(n_probes) / static_cast(n_lists); + ASSERT_TRUE(cuvs::neighbors::eval_neighbours(neighbors_ref_h, + neighbors_h, + distances_ref_h, + distances_h, + n_queries, + n_neighbors, + 0.001, + min_recall)); +}; + +TEST(IvfPqC, BuildSearch) +{ + int64_t n_rows = 8096; + int64_t n_queries = 128; + int64_t n_dim = 32; + uint32_t n_neighbors = 8; + + enum DistanceType metric = L2Expanded; + size_t n_probes = 20; + size_t n_lists = 1024; + + float *index_data, *query_data, *distances_data; + int64_t* neighbors_data; + cudaMalloc(&index_data, sizeof(float) * n_rows * n_dim); + cudaMalloc(&query_data, sizeof(float) * n_queries * n_dim); + cudaMalloc(&neighbors_data, sizeof(int64_t) * n_queries * n_neighbors); + cudaMalloc(&distances_data, sizeof(float) * n_queries * n_neighbors); + + generate_random_data(index_data, n_rows * n_dim); + generate_random_data(query_data, n_queries * n_dim); + + run_ivf_pq(n_rows, + n_queries, + n_dim, + n_neighbors, + index_data, + query_data, + distances_data, + neighbors_data, + metric, + n_probes, + n_lists); + + recall_eval(query_data, + index_data, + neighbors_data, + distances_data, + n_queries, + n_rows, + n_dim, + n_neighbors, + metric, + n_probes, + n_lists); + + // delete device memory + cudaFree(index_data); + cudaFree(query_data); + cudaFree(neighbors_data); + cudaFree(distances_data); +} diff --git a/cpp/test/neighbors/brute_force.cu b/cpp/test/neighbors/brute_force.cu new file mode 100644 index 0000000000..d058bfacef --- /dev/null +++ b/cpp/test/neighbors/brute_force.cu @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include +#include + +namespace cuvs::neighbors::brute_force { +struct KNNInputs { + std::vector> input; + int k; + std::vector labels; +}; + +template +RAFT_KERNEL build_actual_output( + int* output, int n_rows, int k, const int* idx_labels, const IdxT* indices) +{ + int element = threadIdx.x + blockDim.x * blockIdx.x; + if (element >= n_rows * k) return; + + output[element] = idx_labels[indices[element]]; +} + +RAFT_KERNEL build_expected_output(int* output, int n_rows, int k, const int* labels) +{ + int row = threadIdx.x + blockDim.x * blockIdx.x; + if (row >= n_rows) return; + + int cur_label = labels[row]; + for (int i = 0; i < k; i++) { + output[row * k + i] = cur_label; + } +} + +template +class KNNTest : public ::testing::TestWithParam { + public: + KNNTest() + : params_(::testing::TestWithParam::GetParam()), + stream(raft::resource::get_cuda_stream(handle)), + actual_labels_(0, stream), + expected_labels_(0, stream), + input_(0, stream), + search_data_(0, stream), + indices_(0, stream), + distances_(0, stream), + search_labels_(0, stream) + { + } + + protected: + void testBruteForce() + { + // #if (RAFT_ACTIVE_LEVEL >= RAFT_LEVEL_DEBUG) + raft::print_device_vector("Input array: ", input_.data(), rows_ * cols_, std::cout); + std::cout << "K: " << k_ << std::endl; + raft::print_device_vector("Labels array: ", search_labels_.data(), rows_, std::cout); + // #endif + + auto index = raft::make_device_matrix_view( + (const T*)(input_.data()), rows_, cols_); + auto search = raft::make_device_matrix_view( + (const T*)(search_data_.data()), rows_, cols_); + auto indices = + raft::make_device_matrix_view(indices_.data(), rows_, k_); + auto distances = + raft::make_device_matrix_view(distances_.data(), rows_, k_); + + auto metric = cuvs::distance::DistanceType::L2Unexpanded; + auto idx = cuvs::neighbors::brute_force::build(handle, index, metric); + cuvs::neighbors::brute_force::search(handle, idx, search, indices, distances); + + build_actual_output<<>>( + actual_labels_.data(), rows_, k_, search_labels_.data(), indices_.data()); + + build_expected_output<<>>( + expected_labels_.data(), rows_, k_, search_labels_.data()); + + ASSERT_TRUE(devArrMatch( + expected_labels_.data(), actual_labels_.data(), rows_ * k_, cuvs::Compare(), stream)); + } + + void SetUp() override + { + rows_ = params_.input.size(); + cols_ = params_.input[0].size(); + k_ = params_.k; + + actual_labels_.resize(rows_ * k_, stream); + expected_labels_.resize(rows_ * k_, stream); + input_.resize(rows_ * cols_, stream); + search_data_.resize(rows_ * cols_, stream); + indices_.resize(rows_ * k_, stream); + distances_.resize(rows_ * k_, stream); + search_labels_.resize(rows_, stream); + + RAFT_CUDA_TRY( + cudaMemsetAsync(actual_labels_.data(), 0, actual_labels_.size() * sizeof(int), stream)); + RAFT_CUDA_TRY( + cudaMemsetAsync(expected_labels_.data(), 0, expected_labels_.size() * sizeof(int), stream)); + RAFT_CUDA_TRY(cudaMemsetAsync(input_.data(), 0, input_.size() * sizeof(float), stream)); + RAFT_CUDA_TRY( + cudaMemsetAsync(search_data_.data(), 0, search_data_.size() * sizeof(float), stream)); + RAFT_CUDA_TRY(cudaMemsetAsync(indices_.data(), 0, indices_.size() * sizeof(IdxT), stream)); + RAFT_CUDA_TRY(cudaMemsetAsync(distances_.data(), 0, distances_.size() * sizeof(float), stream)); + RAFT_CUDA_TRY( + cudaMemsetAsync(search_labels_.data(), 0, search_labels_.size() * sizeof(int), stream)); + + std::vector row_major_input; + for (std::size_t i = 0; i < params_.input.size(); ++i) { + for (std::size_t j = 0; j < params_.input[i].size(); ++j) { + row_major_input.push_back(params_.input[i][j]); + } + } + rmm::device_buffer input_d = + rmm::device_buffer(row_major_input.data(), row_major_input.size() * sizeof(float), stream); + float* input_ptr = static_cast(input_d.data()); + + rmm::device_buffer labels_d = + rmm::device_buffer(params_.labels.data(), params_.labels.size() * sizeof(int), stream); + int* labels_ptr = static_cast(labels_d.data()); + + raft::copy(input_.data(), input_ptr, rows_ * cols_, stream); + raft::copy(search_data_.data(), input_ptr, rows_ * cols_, stream); + raft::copy(search_labels_.data(), labels_ptr, rows_, stream); + raft::resource::sync_stream(handle, stream); + } + + private: + raft::resources handle; + cudaStream_t stream; + + KNNInputs params_; + int rows_; + int cols_; + rmm::device_uvector input_; + rmm::device_uvector search_data_; + rmm::device_uvector indices_; + rmm::device_uvector distances_; + int k_; + + rmm::device_uvector search_labels_; + rmm::device_uvector actual_labels_; + rmm::device_uvector expected_labels_; +}; + +const std::vector inputs = { + // 2D + {{ + {2.7810836, 2.550537003}, + {1.465489372, 2.362125076}, + {3.396561688, 4.400293529}, + {1.38807019, 1.850220317}, + {3.06407232, 3.005305973}, + {7.627531214, 2.759262235}, + {5.332441248, 2.088626775}, + {6.922596716, 1.77106367}, + {8.675418651, -0.242068655}, + {7.673756466, 3.508563011}, + }, + 2, + {0, 0, 0, 0, 0, 1, 1, 1, 1, 1}}}; + +typedef KNNTest KNNTestFint64_t; +TEST_P(KNNTestFint64_t, BruteForce) { this->testBruteForce(); } + +INSTANTIATE_TEST_CASE_P(KNNTest, KNNTestFint64_t, ::testing::ValuesIn(inputs)); +} // namespace cuvs::neighbors::brute_force \ No newline at end of file diff --git a/cpp/test/neighbors/brute_force_c.cu b/cpp/test/neighbors/brute_force_c.cu new file mode 100644 index 0000000000..7730a98c63 --- /dev/null +++ b/cpp/test/neighbors/brute_force_c.cu @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "ann_utils.cuh" +#include + +extern "C" void run_brute_force(int64_t n_rows, + int64_t n_queries, + int64_t n_dim, + uint32_t n_neighbors, + float* index_data, + float* query_data, + float* distances_data, + int64_t* neighbors_data, + enum DistanceType metric); + +template +void generate_random_data(T* devPtr, size_t size) +{ + raft::handle_t handle; + raft::random::RngState r(1234ULL); + raft::random::uniform(handle, r, devPtr, size, T(0.1), T(2.0)); +}; + +template +void recall_eval(T* query_data, + T* index_data, + IdxT* neighbors, + T* distances, + size_t n_queries, + size_t n_rows, + size_t n_dim, + size_t n_neighbors, + DistanceType metric) +{ + raft::handle_t handle; + auto distances_ref = raft::make_device_matrix(handle, n_queries, n_neighbors); + auto neighbors_ref = raft::make_device_matrix(handle, n_queries, n_neighbors); + cuvs::neighbors::naive_knn( + handle, + distances_ref.data_handle(), + neighbors_ref.data_handle(), + query_data, + index_data, + n_queries, + n_rows, + n_dim, + n_neighbors, + static_cast((uint16_t)metric)); + + size_t size = n_queries * n_neighbors; + std::vector neighbors_h(size); + std::vector distances_h(size); + std::vector neighbors_ref_h(size); + std::vector distances_ref_h(size); + + auto stream = raft::resource::get_cuda_stream(handle); + raft::copy(neighbors_h.data(), neighbors, size, stream); + raft::copy(distances_h.data(), distances, size, stream); + raft::copy(neighbors_ref_h.data(), neighbors_ref.data_handle(), size, stream); + raft::copy(distances_ref_h.data(), distances_ref.data_handle(), size, stream); + + // verify output + double min_recall = 0.95; + ASSERT_TRUE(cuvs::neighbors::eval_neighbours(neighbors_ref_h, + neighbors_h, + distances_ref_h, + distances_h, + n_queries, + n_neighbors, + 0.001, + min_recall)); +}; + +TEST(BruteForceC, BuildSearch) +{ + int64_t n_rows = 8096; + int64_t n_queries = 128; + int64_t n_dim = 32; + uint32_t n_neighbors = 8; + + enum DistanceType metric = L2Expanded; + + float *index_data, *query_data, *distances_data; + int64_t* neighbors_data; + cudaMalloc(&index_data, sizeof(float) * n_rows * n_dim); + cudaMalloc(&query_data, sizeof(float) * n_queries * n_dim); + cudaMalloc(&neighbors_data, sizeof(int64_t) * n_queries * n_neighbors); + cudaMalloc(&distances_data, sizeof(float) * n_queries * n_neighbors); + + generate_random_data(index_data, n_rows * n_dim); + generate_random_data(query_data, n_queries * n_dim); + + run_brute_force(n_rows, + n_queries, + n_dim, + n_neighbors, + index_data, + query_data, + distances_data, + neighbors_data, + metric); + + recall_eval(query_data, + index_data, + neighbors_data, + distances_data, + n_queries, + n_rows, + n_dim, + n_neighbors, + metric); + + // delete device memory + cudaFree(index_data); + cudaFree(query_data); + cudaFree(neighbors_data); + cudaFree(distances_data); +} diff --git a/cpp/test/neighbors/ivf_pq_helpers.cuh b/cpp/test/neighbors/ivf_pq_helpers.cuh new file mode 100644 index 0000000000..79a310f07b --- /dev/null +++ b/cpp/test/neighbors/ivf_pq_helpers.cuh @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace cuvs::neighbors::ivf_pq::helpers { + +using namespace raft; +using namespace raft::neighbors; +using namespace raft::neighbors::ivf_pq; + +/** + * @defgroup ivf_pq_helpers Helper functions for manipulationg IVF PQ Index + * @{ + */ + +namespace codepacker { +/** + * @brief Unpack `n_take` consecutive records of a single list (cluster) in the compressed index + * starting at given `offset`. + * + * Bit compression is removed, which means output will have pq_dim dimensional vectors (one code per + * byte, instead of ceildiv(pq_dim * pq_bits, 8) bytes of pq codes). + * + * Usage example: + * @code{.cpp} + * auto list_data = index.lists()[label]->data.view(); + * // allocate the buffer for the output + * uint32_t n_take = 4; + * auto codes = raft::make_device_matrix(res, n_take, index.pq_dim()); + * uint32_t offset = 0; + * // unpack n_take elements from the list + * ivf_pq::helpers::codepacker::unpack(res, list_data, index.pq_bits(), offset, codes.view()); + * @endcode + * + * @tparam IdxT type of the indices in the source dataset + * + * @param[in] res raft resource + * @param[in] list_data block to read from + * @param[in] pq_bits bit length of encoded vector elements + * @param[in] offset + * How many records in the list to skip. + * @param[out] codes + * the destination buffer [n_take, index.pq_dim()]. + * The length `n_take` defines how many records to unpack, + * it must be smaller than the list size. + */ +inline void unpack( + raft::resources const& res, + device_mdspan::list_extents, row_major> list_data, + uint32_t pq_bits, + uint32_t offset, + device_matrix_view codes) +{ + raft::neighbors::ivf_pq::detail::unpack_list_data( + codes, list_data, offset, pq_bits, resource::get_cuda_stream(res)); +} + +/** + * Write flat PQ codes into an existing list by the given offset. + * + * NB: no memory allocation happens here; the list must fit the data (offset + n_vec). + * + * Usage example: + * @code{.cpp} + * auto list_data = index.lists()[label]->data.view(); + * // allocate the buffer for the input codes + * auto codes = raft::make_device_matrix(res, n_vec, index.pq_dim()); + * ... prepare n_vecs to pack into the list in codes ... + * // write codes into the list starting from the 42nd position + * ivf_pq::helpers::codepacker::pack( + * res, make_const_mdspan(codes.view()), index.pq_bits(), 42, list_data); + * @endcode + * + * @param[in] res + * @param[in] codes flat PQ codes, one code per byte [n_vec, pq_dim] + * @param[in] pq_bits bit length of encoded vector elements + * @param[in] offset how many records to skip before writing the data into the list + * @param[in] list_data block to write into + */ +inline void pack( + raft::resources const& res, + device_matrix_view codes, + uint32_t pq_bits, + uint32_t offset, + device_mdspan::list_extents, row_major> list_data) +{ + raft::neighbors::ivf_pq::detail::pack_list_data( + list_data, codes, offset, pq_bits, resource::get_cuda_stream(res)); +} +} // namespace codepacker + +/** + * Write flat PQ codes into an existing list by the given offset. + * + * The list is identified by its label. + * + * NB: no memory allocation happens here; the list must fit the data (offset + n_vec). + * + * Usage example: + * @code{.cpp} + * // We will write into the 137th cluster + * uint32_t label = 137; + * // allocate the buffer for the input codes + * auto codes = raft::make_device_matrix(res, n_vec, index.pq_dim()); + * ... prepare n_vecs to pack into the list in codes ... + * // write codes into the list starting from the 42nd position + * ivf_pq::helpers::pack_list_data(res, &index, codes_to_pack, label, 42); + * @endcode + * + * @param[in] res + * @param[inout] index IVF-PQ index. + * @param[in] codes flat PQ codes, one code per byte [n_rows, pq_dim] + * @param[in] label The id of the list (cluster) into which we write. + * @param[in] offset how many records to skip before writing the data into the list + */ +template +void pack_list_data(raft::resources const& res, + cuvs::neighbors::ivf_pq::index* index, + device_matrix_view codes, + uint32_t label, + uint32_t offset) +{ + raft::neighbors::ivf_pq::detail::pack_list_data( + res, index->get_raft_index(), codes, label, offset); +} + +/** + * @brief Unpack `n_take` consecutive records of a single list (cluster) in the compressed index + * starting at given `offset`, one code per byte (independently of pq_bits). + * + * Usage example: + * @code{.cpp} + * // We will unpack the fourth cluster + * uint32_t label = 3; + * // Get the list size + * uint32_t list_size = 0; + * raft::copy(&list_size, index.list_sizes().data_handle() + label, 1, + * resource::get_cuda_stream(res)); resource::sync_stream(res); + * // allocate the buffer for the output + * auto codes = raft::make_device_matrix(res, list_size, index.pq_dim()); + * // unpack the whole list + * ivf_pq::helpers::unpack_list_data(res, index, codes.view(), label, 0); + * @endcode + * + * @tparam IdxT type of the indices in the source dataset + * + * @param[in] res + * @param[in] index + * @param[out] out_codes + * the destination buffer [n_take, index.pq_dim()]. + * The length `n_take` defines how many records to unpack, + * it must be smaller than the list size. + * @param[in] label + * The id of the list (cluster) to decode. + * @param[in] offset + * How many records in the list to skip. + */ +template +void unpack_list_data(raft::resources const& res, + const cuvs::neighbors::ivf_pq::index& index, + device_matrix_view out_codes, + uint32_t label, + uint32_t offset) +{ + return raft::neighbors::ivf_pq::detail::unpack_list_data( + res, *index.get_raft_index(), out_codes, label, offset); +} + +/** + * @brief Unpack a series of records of a single list (cluster) in the compressed index + * by their in-list offsets, one code per byte (independently of pq_bits). + * + * Usage example: + * @code{.cpp} + * // We will unpack the fourth cluster + * uint32_t label = 3; + * // Create the selection vector + * auto selected_indices = raft::make_device_vector(res, 4); + * ... fill the indices ... + * resource::sync_stream(res); + * // allocate the buffer for the output + * auto codes = raft::make_device_matrix(res, selected_indices.size(), index.pq_dim()); + * // decode the whole list + * ivf_pq::helpers::unpack_list_data( + * res, index, selected_indices.view(), codes.view(), label); + * @endcode + * + * @tparam IdxT type of the indices in the source dataset + * + * @param[in] res + * @param[in] index + * @param[in] in_cluster_indices + * The offsets of the selected indices within the cluster. + * @param[out] out_codes + * the destination buffer [n_take, index.pq_dim()]. + * The length `n_take` defines how many records to unpack, + * it must be smaller than the list size. + * @param[in] label + * The id of the list (cluster) to decode. + */ +template +void unpack_list_data(raft::resources const& res, + const cuvs::neighbors::ivf_pq::index& index, + device_vector_view in_cluster_indices, + device_matrix_view out_codes, + uint32_t label) +{ + return raft::neighbors::ivf_pq::detail::unpack_list_data( + res, index, out_codes, label, in_cluster_indices); +} + +/** + * @brief Decode `n_take` consecutive records of a single list (cluster) in the compressed index + * starting at given `offset`. + * + * Usage example: + * @code{.cpp} + * // We will reconstruct the fourth cluster + * uint32_t label = 3; + * // Get the list size + * uint32_t list_size = 0; + * raft::copy(&list_size, index.list_sizes().data_handle() + label, 1, + * resource::get_cuda_stream(res)); resource::sync_stream(res); + * // allocate the buffer for the output + * auto decoded_vectors = raft::make_device_matrix(res, list_size, index.dim()); + * // decode the whole list + * ivf_pq::helpers::reconstruct_list_data(res, index, decoded_vectors.view(), label, 0); + * @endcode + * + * @tparam T data element type + * @tparam IdxT type of the indices in the source dataset + * + * @param[in] res + * @param[in] index + * @param[out] out_vectors + * the destination buffer [n_take, index.dim()]. + * The length `n_take` defines how many records to reconstruct, + * it must be smaller than the list size. + * @param[in] label + * The id of the list (cluster) to decode. + * @param[in] offset + * How many records in the list to skip. + */ +template +void reconstruct_list_data(raft::resources const& res, + const cuvs::neighbors::ivf_pq::index& index, + device_matrix_view out_vectors, + uint32_t label, + uint32_t offset) +{ + return raft::neighbors::ivf_pq::detail::reconstruct_list_data( + res, *index.get_raft_index(), out_vectors, label, offset); +} + +/** + * @brief Decode a series of records of a single list (cluster) in the compressed index + * by their in-list offsets. + * + * Usage example: + * @code{.cpp} + * // We will reconstruct the fourth cluster + * uint32_t label = 3; + * // Create the selection vector + * auto selected_indices = raft::make_device_vector(res, 4); + * ... fill the indices ... + * resource::sync_stream(res); + * // allocate the buffer for the output + * auto decoded_vectors = raft::make_device_matrix( + * res, selected_indices.size(), index.dim()); + * // decode the whole list + * ivf_pq::helpers::reconstruct_list_data( + * res, index, selected_indices.view(), decoded_vectors.view(), label); + * @endcode + * + * @tparam T data element type + * @tparam IdxT type of the indices in the source dataset + * + * @param[in] res + * @param[in] index + * @param[in] in_cluster_indices + * The offsets of the selected indices within the cluster. + * @param[out] out_vectors + * the destination buffer [n_take, index.dim()]. + * The length `n_take` defines how many records to reconstruct, + * it must be smaller than the list size. + * @param[in] label + * The id of the list (cluster) to decode. + */ +template +void reconstruct_list_data(raft::resources const& res, + const cuvs::neighbors::ivf_pq::index& index, + device_vector_view in_cluster_indices, + device_matrix_view out_vectors, + uint32_t label) +{ + return raft::neighbors::ivf_pq::detail::reconstruct_list_data( + res, index, out_vectors, label, in_cluster_indices); +} + +/** + * @brief Extend one list of the index in-place, by the list label, skipping the classification and + * encoding steps. + * + * Usage example: + * @code{.cpp} + * // We will extend the fourth cluster + * uint32_t label = 3; + * // We will fill 4 new vectors + * uint32_t n_vec = 4; + * // Indices of the new vectors + * auto indices = raft::make_device_vector(res, n_vec); + * ... fill the indices ... + * auto new_codes = raft::make_device_matrix new_codes( + * res, n_vec, index.pq_dim()); + * ... fill codes ... + * // extend list with new codes + * ivf_pq::helpers::extend_list_with_codes( + * res, &index, codes.view(), indices.view(), label); + * @endcode + * + * @tparam IdxT + * + * @param[in] res + * @param[inout] index + * @param[in] new_codes flat PQ codes, one code per byte [n_rows, index.pq_dim()] + * @param[in] new_indices source indices [n_rows] + * @param[in] label the id of the target list (cluster). + */ +template +void extend_list_with_codes(raft::resources const& res, + cuvs::neighbors::ivf_pq::index* index, + device_matrix_view new_codes, + device_vector_view new_indices, + uint32_t label) +{ + raft::neighbors::ivf_pq::detail::extend_list_with_codes( + res, index->get_raft_index(), new_codes, new_indices, label); +} + +/** + * @brief Extend one list of the index in-place, by the list label, skipping the classification + * step. + * + * Usage example: + * @code{.cpp} + * // We will extend the fourth cluster + * uint32_t label = 3; + * // We will extend with 4 new vectors + * uint32_t n_vec = 4; + * // Indices of the new vectors + * auto indices = raft::make_device_vector(res, n_vec); + * ... fill the indices ... + * auto new_vectors = raft::make_device_matrix new_codes( + * res, n_vec, index.dim()); + * ... fill vectors ... + * // extend list with new vectors + * ivf_pq::helpers::extend_list( + * res, &index, new_vectors.view(), indices.view(), label); + * @endcode + * + * @tparam T + * @tparam IdxT + * + * @param[in] res + * @param[inout] index + * @param[in] new_vectors data to encode [n_rows, index.dim()] + * @param[in] new_indices source indices [n_rows] + * @param[in] label the id of the target list (cluster). + * + */ +template +void extend_list(raft::resources const& res, + cuvs::neighbors::ivf_pq::index* index, + device_matrix_view new_vectors, + device_vector_view new_indices, + uint32_t label) +{ + raft::neighbors::ivf_pq::detail::extend_list( + res, index->get_raft_index(), new_vectors, new_indices, label); +} + +/** + * @brief Remove all data from a single list (cluster) in the index. + * + * Usage example: + * @code{.cpp} + * // We will erase the fourth cluster (label = 3) + * ivf_pq::helpers::erase_list(res, &index, 3); + * @endcode + * + * @tparam IdxT + * @param[in] res + * @param[inout] index + * @param[in] label the id of the target list (cluster). + */ +template +void erase_list(raft::resources const& res, + cuvs::neighbors::ivf_pq::index* index, + uint32_t label) +{ + raft::neighbors::ivf_pq::detail::erase_list(res, index->get_raft_index(), label); +} + +/** @} */ +} // namespace cuvs::neighbors::ivf_pq::helpers diff --git a/cpp/test/neighbors/run_brute_force_c.c b/cpp/test/neighbors/run_brute_force_c.c new file mode 100644 index 0000000000..9c7af13a60 --- /dev/null +++ b/cpp/test/neighbors/run_brute_force_c.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +void run_brute_force(int64_t n_rows, + int64_t n_queries, + int64_t n_dim, + uint32_t n_neighbors, + float* index_data, + float* query_data, + float* distances_data, + int64_t* neighbors_data, + enum DistanceType metric) +{ + // create cuvsResources_t + cuvsResources_t res; + cuvsResourcesCreate(&res); + + // create dataset DLTensor + DLManagedTensor dataset_tensor; + dataset_tensor.dl_tensor.data = index_data; + dataset_tensor.dl_tensor.device.device_type = kDLCUDA; + dataset_tensor.dl_tensor.ndim = 2; + dataset_tensor.dl_tensor.dtype.code = kDLFloat; + dataset_tensor.dl_tensor.dtype.bits = 32; + dataset_tensor.dl_tensor.dtype.lanes = 1; + int64_t dataset_shape[2] = {n_rows, n_dim}; + dataset_tensor.dl_tensor.shape = dataset_shape; + dataset_tensor.dl_tensor.strides = NULL; + + // create index + cuvsBruteForceIndex_t index; + bruteForceIndexCreate(&index); + + // build index + bruteForceBuild(res, &dataset_tensor, metric, 0.0f, index); + + // create queries DLTensor + DLManagedTensor queries_tensor; + queries_tensor.dl_tensor.data = (void*)query_data; + queries_tensor.dl_tensor.device.device_type = kDLCUDA; + queries_tensor.dl_tensor.ndim = 2; + queries_tensor.dl_tensor.dtype.code = kDLFloat; + queries_tensor.dl_tensor.dtype.bits = 32; + queries_tensor.dl_tensor.dtype.lanes = 1; + int64_t queries_shape[2] = {n_queries, n_dim}; + queries_tensor.dl_tensor.shape = queries_shape; + queries_tensor.dl_tensor.strides = NULL; + + // create neighbors DLTensor + DLManagedTensor neighbors_tensor; + neighbors_tensor.dl_tensor.data = (void*)neighbors_data; + neighbors_tensor.dl_tensor.device.device_type = kDLCUDA; + neighbors_tensor.dl_tensor.ndim = 2; + neighbors_tensor.dl_tensor.dtype.code = kDLInt; + neighbors_tensor.dl_tensor.dtype.bits = 64; + neighbors_tensor.dl_tensor.dtype.lanes = 1; + int64_t neighbors_shape[2] = {n_queries, n_neighbors}; + neighbors_tensor.dl_tensor.shape = neighbors_shape; + neighbors_tensor.dl_tensor.strides = NULL; + + // create distances DLTensor + DLManagedTensor distances_tensor; + distances_tensor.dl_tensor.data = (void*)distances_data; + distances_tensor.dl_tensor.device.device_type = kDLCUDA; + distances_tensor.dl_tensor.ndim = 2; + distances_tensor.dl_tensor.dtype.code = kDLFloat; + distances_tensor.dl_tensor.dtype.bits = 32; + distances_tensor.dl_tensor.dtype.lanes = 1; + int64_t distances_shape[2] = {n_queries, n_neighbors}; + distances_tensor.dl_tensor.shape = distances_shape; + distances_tensor.dl_tensor.strides = NULL; + + // search index + bruteForceSearch(res, index, &queries_tensor, &neighbors_tensor, &distances_tensor); + + // de-allocate index and res + bruteForceIndexDestroy(index); + cuvsResourcesDestroy(res); +} diff --git a/cpp/test/neighbors/run_ivf_flat_c.c b/cpp/test/neighbors/run_ivf_flat_c.c new file mode 100644 index 0000000000..badb507a50 --- /dev/null +++ b/cpp/test/neighbors/run_ivf_flat_c.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +void run_ivf_flat(int64_t n_rows, + int64_t n_queries, + int64_t n_dim, + uint32_t n_neighbors, + float* index_data, + float* query_data, + float* distances_data, + int64_t* neighbors_data, + enum DistanceType metric, + size_t n_probes, + size_t n_lists) +{ + // create cuvsResources_t + cuvsResources_t res; + cuvsResourcesCreate(&res); + + // create dataset DLTensor + DLManagedTensor dataset_tensor; + dataset_tensor.dl_tensor.data = index_data; + dataset_tensor.dl_tensor.device.device_type = kDLCUDA; + dataset_tensor.dl_tensor.ndim = 2; + dataset_tensor.dl_tensor.dtype.code = kDLFloat; + dataset_tensor.dl_tensor.dtype.bits = 32; + dataset_tensor.dl_tensor.dtype.lanes = 1; + int64_t dataset_shape[2] = {n_rows, n_dim}; + dataset_tensor.dl_tensor.shape = dataset_shape; + dataset_tensor.dl_tensor.strides = NULL; + + // create index + cuvsIvfFlatIndex_t index; + ivfFlatIndexCreate(&index); + + // build index + cuvsIvfFlatIndexParams_t build_params; + cuvsIvfFlatIndexParamsCreate(&build_params); + build_params->metric = metric; + build_params->n_lists = n_lists; + ivfFlatBuild(res, build_params, &dataset_tensor, index); + + // create queries DLTensor + DLManagedTensor queries_tensor; + queries_tensor.dl_tensor.data = (void*)query_data; + queries_tensor.dl_tensor.device.device_type = kDLCUDA; + queries_tensor.dl_tensor.ndim = 2; + queries_tensor.dl_tensor.dtype.code = kDLFloat; + queries_tensor.dl_tensor.dtype.bits = 32; + queries_tensor.dl_tensor.dtype.lanes = 1; + int64_t queries_shape[2] = {n_queries, n_dim}; + queries_tensor.dl_tensor.shape = queries_shape; + queries_tensor.dl_tensor.strides = NULL; + + // create neighbors DLTensor + DLManagedTensor neighbors_tensor; + neighbors_tensor.dl_tensor.data = (void*)neighbors_data; + neighbors_tensor.dl_tensor.device.device_type = kDLCUDA; + neighbors_tensor.dl_tensor.ndim = 2; + neighbors_tensor.dl_tensor.dtype.code = kDLInt; + neighbors_tensor.dl_tensor.dtype.bits = 64; + neighbors_tensor.dl_tensor.dtype.lanes = 1; + int64_t neighbors_shape[2] = {n_queries, n_neighbors}; + neighbors_tensor.dl_tensor.shape = neighbors_shape; + neighbors_tensor.dl_tensor.strides = NULL; + + // create distances DLTensor + DLManagedTensor distances_tensor; + distances_tensor.dl_tensor.data = (void*)distances_data; + distances_tensor.dl_tensor.device.device_type = kDLCUDA; + distances_tensor.dl_tensor.ndim = 2; + distances_tensor.dl_tensor.dtype.code = kDLFloat; + distances_tensor.dl_tensor.dtype.bits = 32; + distances_tensor.dl_tensor.dtype.lanes = 1; + int64_t distances_shape[2] = {n_queries, n_neighbors}; + distances_tensor.dl_tensor.shape = distances_shape; + distances_tensor.dl_tensor.strides = NULL; + + // search index + cuvsIvfFlatSearchParams_t search_params; + cuvsIvfFlatSearchParamsCreate(&search_params); + search_params->n_probes = n_probes; + ivfFlatSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); + + // de-allocate index and res + cuvsIvfFlatSearchParamsDestroy(search_params); + cuvsIvfFlatIndexParamsDestroy(build_params); + ivfFlatIndexDestroy(index); + cuvsResourcesDestroy(res); +} diff --git a/cpp/test/neighbors/run_ivf_pq_c.c b/cpp/test/neighbors/run_ivf_pq_c.c new file mode 100644 index 0000000000..fece4a644c --- /dev/null +++ b/cpp/test/neighbors/run_ivf_pq_c.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +void run_ivf_pq(int64_t n_rows, + int64_t n_queries, + int64_t n_dim, + uint32_t n_neighbors, + float* index_data, + float* query_data, + float* distances_data, + int64_t* neighbors_data, + enum DistanceType metric, + size_t n_probes, + size_t n_lists) +{ + // create cuvsResources_t + cuvsResources_t res; + cuvsResourcesCreate(&res); + + // create dataset DLTensor + DLManagedTensor dataset_tensor; + dataset_tensor.dl_tensor.data = index_data; + dataset_tensor.dl_tensor.device.device_type = kDLCUDA; + dataset_tensor.dl_tensor.ndim = 2; + dataset_tensor.dl_tensor.dtype.code = kDLFloat; + dataset_tensor.dl_tensor.dtype.bits = 32; + dataset_tensor.dl_tensor.dtype.lanes = 1; + int64_t dataset_shape[2] = {n_rows, n_dim}; + dataset_tensor.dl_tensor.shape = dataset_shape; + dataset_tensor.dl_tensor.strides = NULL; + + // create index + cuvsIvfPqIndex_t index; + ivfPqIndexCreate(&index); + + // build index + cuvsIvfPqIndexParams_t build_params; + cuvsIvfPqIndexParamsCreate(&build_params); + build_params->metric = metric; + build_params->n_lists = n_lists; + ivfPqBuild(res, build_params, &dataset_tensor, index); + + // create queries DLTensor + DLManagedTensor queries_tensor; + queries_tensor.dl_tensor.data = (void*)query_data; + queries_tensor.dl_tensor.device.device_type = kDLCUDA; + queries_tensor.dl_tensor.ndim = 2; + queries_tensor.dl_tensor.dtype.code = kDLFloat; + queries_tensor.dl_tensor.dtype.bits = 32; + queries_tensor.dl_tensor.dtype.lanes = 1; + int64_t queries_shape[2] = {n_queries, n_dim}; + queries_tensor.dl_tensor.shape = queries_shape; + queries_tensor.dl_tensor.strides = NULL; + + // create neighbors DLTensor + DLManagedTensor neighbors_tensor; + neighbors_tensor.dl_tensor.data = (void*)neighbors_data; + neighbors_tensor.dl_tensor.device.device_type = kDLCUDA; + neighbors_tensor.dl_tensor.ndim = 2; + neighbors_tensor.dl_tensor.dtype.code = kDLInt; + neighbors_tensor.dl_tensor.dtype.bits = 64; + neighbors_tensor.dl_tensor.dtype.lanes = 1; + int64_t neighbors_shape[2] = {n_queries, n_neighbors}; + neighbors_tensor.dl_tensor.shape = neighbors_shape; + neighbors_tensor.dl_tensor.strides = NULL; + + // create distances DLTensor + DLManagedTensor distances_tensor; + distances_tensor.dl_tensor.data = (void*)distances_data; + distances_tensor.dl_tensor.device.device_type = kDLCUDA; + distances_tensor.dl_tensor.ndim = 2; + distances_tensor.dl_tensor.dtype.code = kDLFloat; + distances_tensor.dl_tensor.dtype.bits = 32; + distances_tensor.dl_tensor.dtype.lanes = 1; + int64_t distances_shape[2] = {n_queries, n_neighbors}; + distances_tensor.dl_tensor.shape = distances_shape; + distances_tensor.dl_tensor.strides = NULL; + + // search index + cuvsIvfPqSearchParams_t search_params; + cuvsIvfPqSearchParamsCreate(&search_params); + search_params->n_probes = n_probes; + ivfPqSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); + + // de-allocate index and res + cuvsIvfPqSearchParamsDestroy(search_params); + cuvsIvfPqIndexParamsDestroy(build_params); + ivfPqIndexDestroy(index); + cuvsResourcesDestroy(res); +} diff --git a/docs/source/c_api/neighbors.rst b/docs/source/c_api/neighbors.rst index 6bbd3cc7f3..dc55a74dca 100644 --- a/docs/source/c_api/neighbors.rst +++ b/docs/source/c_api/neighbors.rst @@ -9,6 +9,7 @@ Nearest Neighbors :maxdepth: 2 :caption: Contents: - - - neighbors_cagra_c.rst \ No newline at end of file + neighbors_bruteforce_c.rst + neighbors_ivf_flat_c.rst + neighbors_ivf_pq_c.rst + neighbors_cagra_c.rst diff --git a/docs/source/c_api/neighbors_bruteforce_c.rst b/docs/source/c_api/neighbors_bruteforce_c.rst new file mode 100644 index 0000000000..af0356eee4 --- /dev/null +++ b/docs/source/c_api/neighbors_bruteforce_c.rst @@ -0,0 +1,34 @@ +Bruteforce +========== + +The bruteforce method is running the KNN algorithm. It performs an extensive search, and in contrast to ANN methods produces an exact result. + +.. role:: py(code) + :language: c + :class: highlight + +``#include `` + +Index +----- + +.. doxygengroup:: bruteforce_c_index + :project: cuvs + :members: + :content-only: + +Index build +----------- + +.. doxygengroup:: bruteforce_c_index_build + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: bruteforce_c_index_search + :project: cuvs + :members: + :content-only: diff --git a/docs/source/c_api/neighbors_ivf_flat_c.rst b/docs/source/c_api/neighbors_ivf_flat_c.rst new file mode 100644 index 0000000000..9e1ccc0d1d --- /dev/null +++ b/docs/source/c_api/neighbors_ivf_flat_c.rst @@ -0,0 +1,50 @@ +IVF-Flat +======== + +The IVF-Flat method is an ANN algorithm. It uses an inverted file index (IVF) with unmodified (that is, flat) vectors. This algorithm provides simple knobs to reduce the overall search space and to trade-off accuracy for speed. + +.. role:: py(code) + :language: c + :class: highlight + +``#include `` + +Index build parameters +---------------------- + +.. doxygengroup:: ivf_flat_c_index_params + :project: cuvs + :members: + :content-only: + +Index search parameters +----------------------- + +.. doxygengroup:: ivf_flat_c_search_params + :project: cuvs + :members: + :content-only: + +Index +----- + +.. doxygengroup:: ivf_flat_c_index + :project: cuvs + :members: + :content-only: + +Index build +----------- + +.. doxygengroup:: ivf_flat_c_index_build + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: ivf_flat_c_index_search + :project: cuvs + :members: + :content-only: diff --git a/docs/source/c_api/neighbors_ivf_pq_c.rst b/docs/source/c_api/neighbors_ivf_pq_c.rst new file mode 100644 index 0000000000..0707196098 --- /dev/null +++ b/docs/source/c_api/neighbors_ivf_pq_c.rst @@ -0,0 +1,50 @@ +IVF-PQ +====== + +The IVF-PQ method is an ANN algorithm. Like IVF-Flat, IVF-PQ splits the points into a number of clusters (also specified by a parameter called n_lists) and searches the closest clusters to compute the nearest neighbors (also specified by a parameter called n_probes), but it shrinks the sizes of the vectors using a technique called product quantization. + +.. role:: py(code) + :language: c + :class: highlight + +``#include `` + +Index build parameters +---------------------- + +.. doxygengroup:: ivf_pq_c_index_params + :project: cuvs + :members: + :content-only: + +Index search parameters +----------------------- + +.. doxygengroup:: ivf_pq_c_search_params + :project: cuvs + :members: + :content-only: + +Index +----- + +.. doxygengroup:: ivf_pq_c_index + :project: cuvs + :members: + :content-only: + +Index build +----------- + +.. doxygengroup:: ivf_pq_c_index_build + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: ivf_pq_c_index_search + :project: cuvs + :members: + :content-only: diff --git a/docs/source/cpp_api.rst b/docs/source/cpp_api.rst index 67bc7f65b2..e04fff0b87 100644 --- a/docs/source/cpp_api.rst +++ b/docs/source/cpp_api.rst @@ -7,4 +7,5 @@ C++ API Documentation .. toctree:: :maxdepth: 4 + cpp_api/distance.rst cpp_api/neighbors.rst diff --git a/docs/source/cpp_api/distance.rst b/docs/source/cpp_api/distance.rst new file mode 100644 index 0000000000..21f4558ecd --- /dev/null +++ b/docs/source/cpp_api/distance.rst @@ -0,0 +1,19 @@ +Distance +======== + +This page provides C++ class references for the publicly-exposed elements of the `cuvs/distance` package. cuVS's +distances have been highly optimized and support a wide assortment of different distance measures. + +.. role:: py(code) + :language: c++ + :class: highlight + +Distance Types +-------------- + +``#include `` + +namespace *cuvs::distance* + +.. doxygenenum:: cuvs::distance::DistanceType + :project: cuvs diff --git a/docs/source/cpp_api/neighbors.rst b/docs/source/cpp_api/neighbors.rst index 61898cec86..f9006412c9 100644 --- a/docs/source/cpp_api/neighbors.rst +++ b/docs/source/cpp_api/neighbors.rst @@ -9,4 +9,7 @@ Nearest Neighbors :maxdepth: 2 :caption: Contents: - neighbors_cagra.rst \ No newline at end of file + neighbors_bruteforce.rst + neighbors_ivf_flat.rst + neighbors_ivf_pq.rst + neighbors_cagra.rst diff --git a/docs/source/cpp_api/neighbors_bruteforce.rst b/docs/source/cpp_api/neighbors_bruteforce.rst new file mode 100644 index 0000000000..3adcb01c52 --- /dev/null +++ b/docs/source/cpp_api/neighbors_bruteforce.rst @@ -0,0 +1,36 @@ +Bruteforce +========== + +The bruteforce method is running the KNN algorithm. It performs an extensive search, and in contrast to ANN methods produces an exact result. + +.. role:: py(code) + :language: c++ + :class: highlight + +``#include `` + +namespace *cuvs::neighbors::bruteforce* + +Index +----- + +.. doxygengroup:: bruteforce_cpp_index + :project: cuvs + :members: + :content-only: + +Index build +----------- + +.. doxygengroup:: bruteforce_cpp_index_build + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: bruteforce_cpp_index_search + :project: cuvs + :members: + :content-only: diff --git a/docs/source/cpp_api/neighbors_ivf_flat.rst b/docs/source/cpp_api/neighbors_ivf_flat.rst new file mode 100644 index 0000000000..3836223e10 --- /dev/null +++ b/docs/source/cpp_api/neighbors_ivf_flat.rst @@ -0,0 +1,68 @@ +IVF-Flat +======== + +The IVF-Flat method is an ANN algorithm. It uses an inverted file index (IVF) with unmodified (that is, flat) vectors. This algorithm provides simple knobs to reduce the overall search space and to trade-off accuracy for speed. + +.. role:: py(code) + :language: c++ + :class: highlight + +``#include `` + +namespace *cuvs::neighbors::ivf_flat* + +Index build parameters +---------------------- + +.. doxygengroup:: ivf_flat_cpp_index_params + :project: cuvs + :members: + :content-only: + +Index search parameters +----------------------- + +.. doxygengroup:: ivf_flat_cpp_search_params + :project: cuvs + :members: + :content-only: + +Index +----- + +.. doxygengroup:: ivf_flat_cpp_index + :project: cuvs + :members: + :content-only: + +Index build +----------- + +.. doxygengroup:: ivf_flat_cpp_index_build + :project: cuvs + :members: + :content-only: + +Index extend +------------ + +.. doxygengroup:: ivf_flat_cpp_index_extend + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: ivf_flat_cpp_index_search + :project: cuvs + :members: + :content-only: + +Index serialize +--------------- + +.. doxygengroup:: ivf_flat_cpp_serialize + :project: cuvs + :members: + :content-only: diff --git a/docs/source/cpp_api/neighbors_ivf_pq.rst b/docs/source/cpp_api/neighbors_ivf_pq.rst new file mode 100644 index 0000000000..0d4d7061a6 --- /dev/null +++ b/docs/source/cpp_api/neighbors_ivf_pq.rst @@ -0,0 +1,68 @@ +IVF-PQ +====== + +The IVF-PQ method is an ANN algorithm. Like IVF-Flat, IVF-PQ splits the points into a number of clusters (also specified by a parameter called n_lists) and searches the closest clusters to compute the nearest neighbors (also specified by a parameter called n_probes), but it shrinks the sizes of the vectors using a technique called product quantization. + +.. role:: py(code) + :language: c++ + :class: highlight + +``#include `` + +namespace *cuvs::neighbors::ivf_pq* + +Index build parameters +---------------------- + +.. doxygengroup:: ivf_pq_cpp_index_params + :project: cuvs + :members: + :content-only: + +Index search parameters +----------------------- + +.. doxygengroup:: ivf_pq_cpp_search_params + :project: cuvs + :members: + :content-only: + +Index +----- + +.. doxygengroup:: ivf_pq_cpp_index + :project: cuvs + :members: + :content-only: + +Index build +----------- + +.. doxygengroup:: ivf_pq_cpp_index_build + :project: cuvs + :members: + :content-only: + +Index extend +------------ + +.. doxygengroup:: ivf_pq_cpp_index_extend + :project: cuvs + :members: + :content-only: + +Index search +------------ + +.. doxygengroup:: ivf_pq_cpp_index_search + :project: cuvs + :members: + :content-only: + +Index serialize +--------------- + +.. doxygengroup:: ivf_pq_cpp_serialize + :project: cuvs + :members: + :content-only: From b25c184557a70e1a5ce6e4fe71dd22a52c57e2a7 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Thu, 21 Mar 2024 10:33:12 -0400 Subject: [PATCH 021/623] Update pre-commit-hooks to v0.0.3 (#60) This fixes an issue with how the `verify-copyright` hook handles multiple merge bases. Authors: - Kyle Edwards (https://github.com/KyleFromNVIDIA) Approvers: - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cuvs/pull/60 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce5e8e0e4a..576b53907a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -102,7 +102,7 @@ repos: hooks: - id: check-json - repo: https://github.com/rapidsai/pre-commit-hooks - rev: v0.0.1 + rev: v0.0.3 hooks: - id: verify-copyright files: | From 724ce680218a5598a56b64f9d95f02eadcce46f2 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Thu, 21 Mar 2024 12:17:56 -0700 Subject: [PATCH 022/623] expose cuvsResources_t to python (#58) The current python code was creating a cuvsResources_t to pass to the CAGRA apis on the build/search code. This both ignored the passed in `resources` object, and also leaked memory since it wasn't calling cuvsResourcesDestroy. Fix by exposing the cuvsResources_t object to python. Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Divye Gala (https://github.com/divyegala) URL: https://github.com/rapidsai/cuvs/pull/58 --- python/cuvs/cuvs/common/CMakeLists.txt | 2 +- python/cuvs/cuvs/common/__init__.py | 4 +- python/cuvs/cuvs/common/c_api.pxd | 1 + python/cuvs/cuvs/common/resources.pxd | 22 ++++ python/cuvs/cuvs/common/resources.pyx | 120 +++++++++++++++++++++ python/cuvs/cuvs/neighbors/cagra/cagra.pyx | 35 ++---- python/cuvs/cuvs/test/test_doctests.py | 1 + 7 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 python/cuvs/cuvs/common/resources.pxd create mode 100644 python/cuvs/cuvs/common/resources.pyx diff --git a/python/cuvs/cuvs/common/CMakeLists.txt b/python/cuvs/cuvs/common/CMakeLists.txt index b477fdb322..066a73d351 100644 --- a/python/cuvs/cuvs/common/CMakeLists.txt +++ b/python/cuvs/cuvs/common/CMakeLists.txt @@ -13,7 +13,7 @@ # ============================================================================= # Set the list of Cython files to build -set(cython_sources cydlpack.pyx exceptions.pyx) +set(cython_sources cydlpack.pyx exceptions.pyx resources.pyx) set(linked_libraries cuvs::cuvs cuvs_c) # Build all of the Cython targets diff --git a/python/cuvs/cuvs/common/__init__.py b/python/cuvs/cuvs/common/__init__.py index eb5666659f..e66418306b 100644 --- a/python/cuvs/cuvs/common/__init__.py +++ b/python/cuvs/cuvs/common/__init__.py @@ -13,6 +13,6 @@ # limitations under the License. -from .temp_raft import auto_sync_resources +from .resources import Resources, auto_sync_resources -__all__ = ["auto_sync_resources"] +__all__ = ["auto_sync_resources", "Resources"] diff --git a/python/cuvs/cuvs/common/c_api.pxd b/python/cuvs/cuvs/common/c_api.pxd index e4e62bf944..f99fd5348e 100644 --- a/python/cuvs/cuvs/common/c_api.pxd +++ b/python/cuvs/cuvs/common/c_api.pxd @@ -30,4 +30,5 @@ cdef extern from "cuvs/core/c_api.h": cuvsError_t cuvsResourcesCreate(cuvsResources_t* res) cuvsError_t cuvsResourcesDestroy(cuvsResources_t res) cuvsError_t cuvsStreamSet(cuvsResources_t res, cudaStream_t stream) + cuvsError_t cuvsStreamSync(cuvsResources_t res) const char * cuvsGetLastErrorText() diff --git a/python/cuvs/cuvs/common/resources.pxd b/python/cuvs/cuvs/common/resources.pxd new file mode 100644 index 0000000000..69a5c894f4 --- /dev/null +++ b/python/cuvs/cuvs/common/resources.pxd @@ -0,0 +1,22 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +from cuvs.common.c_api cimport cuvsResources_t + + +cdef class Resources: + cdef cuvsResources_t c_obj diff --git a/python/cuvs/cuvs/common/resources.pyx b/python/cuvs/cuvs/common/resources.pyx new file mode 100644 index 0000000000..c0b72ae348 --- /dev/null +++ b/python/cuvs/cuvs/common/resources.pyx @@ -0,0 +1,120 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +import functools + +from cuda.ccudart cimport cudaStream_t + +from cuvs.common.c_api cimport ( + cuvsResources_t, + cuvsResourcesCreate, + cuvsResourcesDestroy, + cuvsStreamSet, + cuvsStreamSync, +) + +from cuvs.common.exceptions import check_cuvs + + +cdef class Resources: + """ + Resources is a lightweight python wrapper around the corresponding + C++ class of resources exposed by RAFT's C++ interface. Refer to + the header file raft/core/resources.hpp for interface level + details of this struct. + + Parameters + ---------- + stream : Optional stream to use for ordering CUDA instructions + + Examples + -------- + + Basic usage: + + >>> from cuvs.common import Resources + >>> handle = Resources() + >>> + >>> # call algos here + >>> + >>> # final sync of all work launched in the stream of this handle + >>> handle.sync() + + Using a cuPy stream with cuVS Resources: + + >>> import cupy + >>> from cuvs.common import Resources + >>> + >>> cupy_stream = cupy.cuda.Stream() + >>> handle = Resources(stream=cupy_stream.ptr) + """ + + def __cinit__(self, stream=None): + check_cuvs(cuvsResourcesCreate(&self.c_obj)) + if stream: + check_cuvs(cuvsStreamSet(self.c_obj, stream)) + + def sync(self): + check_cuvs(cuvsStreamSync(self.c_obj)) + + def get_c_obj(self): + """ + Return the pointer to the underlying c_obj as a size_t + """ + return self.c_obj + + def __dealloc__(self): + check_cuvs(cuvsResourcesDestroy(self.c_obj)) + + +_resources_param_string = """ + resources : Optional cuVS Resource handle for reusing CUDA resources. + If Resources aren't supplied, CUDA resources will be + allocated inside this function and synchronized before the + function exits. If resources are supplied, you will need to + explicitly synchronize yourself by calling `resources.sync()` + before accessing the output. +""".strip() + + +def auto_sync_resources(f): + """Decorator to automatically call sync on a cuVS Resources object when + it isn't passed to a function. + + When a resources=None is passed to the wrapped function, this decorator + will automatically create a default resources for the function, and + call sync on that resources when the function exits. + + This will also insert the appropriate docstring for the resources parameter + """ + + @functools.wraps(f) + def wrapper(*args, resources=None, **kwargs): + sync_resources = resources is None + resources = resources if resources is not None else Resources() + + ret_value = f(*args, resources=resources, **kwargs) + + if sync_resources: + resources.sync() + + return ret_value + + wrapper.__doc__ = wrapper.__doc__.format( + resources_docstring=_resources_param_string + ) + return wrapper diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx index d64ac72ee0..818c93940a 100644 --- a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx @@ -19,20 +19,14 @@ import numpy as np cimport cuvs.common.cydlpack -from cuvs.common.temp_raft import auto_sync_resources +from cuvs.common.resources import auto_sync_resources from cython.operator cimport dereference as deref from libcpp cimport bool, cast from cuvs.common cimport cydlpack -from pylibraft.common import ( - DeviceResources, - Stream, - auto_convert_output, - cai_wrapper, - device_ndarray, -) +from pylibraft.common import auto_convert_output, cai_wrapper, device_ndarray from pylibraft.common.cai_wrapper import wrap_array from pylibraft.common.interruptible import cuda_interruptible from pylibraft.neighbors.common import _check_input_array @@ -46,12 +40,6 @@ from libc.stdint cimport ( uintptr_t, ) -from cuvs.common.c_api cimport ( - cuvsError_t, - cuvsResources_t, - cuvsResourcesCreate, -) - from cuvs.common.exceptions import check_cuvs @@ -86,7 +74,6 @@ cdef class IndexParams: graph_degree=64, build_algo="ivf_pq", nn_descent_niter=20): - cuvsCagraIndexParamsCreate(&self.params) # todo (dgd): enable once other metrics are present @@ -136,7 +123,6 @@ cdef class Index: check_cuvs(cuvsCagraIndexCreate(&self.index)) def __dealloc__(self): - cdef cuvsError_t index_destroy_status if self.index is not NULL: check_cuvs(cuvsCagraIndexDestroy(self.index)) @@ -202,20 +188,17 @@ def build_index(IndexParams index_params, dataset, resources=None): _check_input_array(dataset_ai, [np.dtype('float32'), np.dtype('byte'), np.dtype('ubyte')]) - cdef cuvsResources_t res_ - cdef cuvsError_t cstat - - check_cuvs(cuvsResourcesCreate(&res_)) - cdef Index idx = Index() cdef cuvsError_t build_status cdef cydlpack.DLManagedTensor* dataset_dlpack = \ cydlpack.dlpack_c(dataset_ai) cdef cuvsCagraIndexParams* params = index_params.params + cdef cuvsResources_t res = resources.get_c_obj() + with cuda_interruptible(): check_cuvs(cuvsCagraBuild( - res_, + res, params, dataset_dlpack, idx.index @@ -445,11 +428,6 @@ def search(SearchParams search_params, if not index.trained: raise ValueError("Index needs to be built before calling search.") - cdef cuvsResources_t res_ - cdef cuvsError_t cstat - - check_cuvs(cuvsResourcesCreate(&res_)) - # todo(dgd): we can make the check of dtype a parameter of wrap_array # in RAFT to make this a single call queries_cai = wrap_array(queries) @@ -480,10 +458,11 @@ def search(SearchParams search_params, cydlpack.dlpack_c(neighbors_cai) cdef cydlpack.DLManagedTensor* distances_dlpack = \ cydlpack.dlpack_c(distances_cai) + cdef cuvsResources_t res = resources.get_c_obj() with cuda_interruptible(): check_cuvs(cuvsCagraSearch( - res_, + res, params, index.index, queries_dlpack, diff --git a/python/cuvs/cuvs/test/test_doctests.py b/python/cuvs/cuvs/test/test_doctests.py index 6d56ffaa2f..d334aa3228 100644 --- a/python/cuvs/cuvs/test/test_doctests.py +++ b/python/cuvs/cuvs/test/test_doctests.py @@ -90,6 +90,7 @@ def _find_doctests_in_obj(obj, finder=None, criteria=None): # doctests for here DOC_STRINGS = list(_find_doctests_in_obj(cuvs.neighbors)) DOC_STRINGS.extend(_find_doctests_in_obj(cuvs.neighbors.cagra)) +DOC_STRINGS.extend(_find_doctests_in_obj(cuvs.common)) @pytest.mark.parametrize( From 21e6d89d98273be6b8cdfb3849ee574aeef5f9ff Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Mon, 1 Apr 2024 17:49:36 -0500 Subject: [PATCH 023/623] Use `conda env create --yes` instead of `--force` (#62) conda dropped support for the `--force` flag to `conda env create`. This changes that flag name to `--yes`. See https://github.com/conda/conda/blob/main/CHANGELOG.md#2430-2024-03-12 and https://github.com/rapidsai/miniforge-cuda/pull/63 for more info. Authors: - Bradley Dice (https://github.com/bdice) Approvers: - Ray Douglass (https://github.com/raydouglass) - https://github.com/jakirkham URL: https://github.com/rapidsai/cuvs/pull/62 --- ci/build_docs.sh | 2 +- ci/build_rust.sh | 2 +- ci/check_style.sh | 2 +- ci/test_cpp.sh | 2 +- ci/test_python.sh | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 9b371d1f9e..9d4509e08a 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -11,7 +11,7 @@ rapids-dependency-file-generator \ --file_key docs \ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" | tee env.yaml -rapids-mamba-retry env create --force -f env.yaml -n docs +rapids-mamba-retry env create --yes -f env.yaml -n docs # seeing failures on activating the environment here on unbound locals # apply workaround from https://github.com/conda/conda/issues/8186#issuecomment-532874667 diff --git a/ci/build_rust.sh b/ci/build_rust.sh index ad2202734a..dc4d4cf720 100755 --- a/ci/build_rust.sh +++ b/ci/build_rust.sh @@ -11,7 +11,7 @@ rapids-dependency-file-generator \ --file_key rust \ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" | tee env.yaml -rapids-mamba-retry env create --force -f env.yaml -n rust +rapids-mamba-retry env create --yes -f env.yaml -n rust # seeing failures on activating the environment here on unbound locals # apply workaround from https://github.com/conda/conda/issues/8186#issuecomment-532874667 diff --git a/ci/check_style.sh b/ci/check_style.sh index 0ee6e88e58..58cc1b944a 100755 --- a/ci/check_style.sh +++ b/ci/check_style.sh @@ -11,7 +11,7 @@ rapids-dependency-file-generator \ --file_key checks \ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" | tee env.yaml -rapids-mamba-retry env create --force -f env.yaml -n checks +rapids-mamba-retry env create --yes -f env.yaml -n checks conda activate checks # Run pre-commit checks diff --git a/ci/test_cpp.sh b/ci/test_cpp.sh index 95ae6a69bd..75ec7c5b65 100755 --- a/ci/test_cpp.sh +++ b/ci/test_cpp.sh @@ -11,7 +11,7 @@ rapids-dependency-file-generator \ --file_key test_cpp \ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch)" | tee env.yaml -rapids-mamba-retry env create --force -f env.yaml -n test +rapids-mamba-retry env create --yes -f env.yaml -n test # Temporarily allow unbound variables for conda activation. set +u diff --git a/ci/test_python.sh b/ci/test_python.sh index 17fbed4243..1c23c92273 100755 --- a/ci/test_python.sh +++ b/ci/test_python.sh @@ -11,7 +11,7 @@ rapids-dependency-file-generator \ --file_key test_python \ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" | tee env.yaml -rapids-mamba-retry env create --force -f env.yaml -n test +rapids-mamba-retry env create --yes -f env.yaml -n test # Temporarily allow unbound variables for conda activation. set +u From 813c54fdf3524e583e81c6596cf5471bb105f1f6 Mon Sep 17 00:00:00 2001 From: Yinze <37508512+yinze00@users.noreply.github.com> Date: Wed, 3 Apr 2024 04:31:58 +0800 Subject: [PATCH 024/623] [fix] link anchor of "Installing cuVS" (#64) This PR fixes the broken anchor link for 'installing cuVS' in the Readme file Authors: - Yinze (https://github.com/yinze00) - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/64 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 160f466042..6ded5515c7 100755 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ 1. [Useful Resources](#useful-resources) 2. [What is cuVS?](#what-is-cuvs) -3. [Installing cuVS](#installing) +3. [Installing cuVS](#installing-cuvs) 4. [Getting Started](#getting-started) 5. [Contributing](#contributing) 6. [References](#references) From 1fa8bef86a955a0d8b7848e0ae4922775d4e7e97 Mon Sep 17 00:00:00 2001 From: "Artem M. Chirkin" <9253178+achirkin@users.noreply.github.com> Date: Tue, 2 Apr 2024 22:49:34 +0200 Subject: [PATCH 025/623] Add CAGRA-Q compression (#61) Minimal code changes to add CAGRA-Q compression to the CAGRA index. When built with compression, the index uses the compressed dataset automatically during search. At this moment, there's not new search parameters compared to standard CAGRA. Authors: - Artem M. Chirkin (https://github.com/achirkin) Approvers: - Ben Frederickson (https://github.com/benfred) - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/61 --- cpp/include/cuvs/neighbors/cagra.h | 61 +++++++++++++++++++++++++ cpp/include/cuvs/neighbors/cagra.hpp | 59 ++++++++++++++++++++++++- cpp/src/neighbors/cagra_c.cpp | 31 ++++++++++++- cpp/test/neighbors/ann_cagra.cuh | 66 ++++++++++++++++++++++------ 4 files changed, 202 insertions(+), 15 deletions(-) diff --git a/cpp/include/cuvs/neighbors/cagra.h b/cpp/include/cuvs/neighbors/cagra.h index 912430a47b..3a1a8c9fec 100644 --- a/cpp/include/cuvs/neighbors/cagra.h +++ b/cpp/include/cuvs/neighbors/cagra.h @@ -40,6 +40,45 @@ enum cuvsCagraGraphBuildAlgo { NN_DESCENT }; +/** Parameters for VPQ compression. */ +struct cuvsCagraCompressionParams { + /** + * The bit length of the vector element after compression by PQ. + * + * Possible values: [4, 5, 6, 7, 8]. + * + * Hint: the smaller the 'pq_bits', the smaller the index size and the better the search + * performance, but the lower the recall. + */ + uint32_t pq_bits; + /** + * The dimensionality of the vector after compression by PQ. + * When zero, an optimal value is selected using a heuristic. + * + * TODO: at the moment `dim` must be a multiple `pq_dim`. + */ + uint32_t pq_dim; + /** + * Vector Quantization (VQ) codebook size - number of "coarse cluster centers". + * When zero, an optimal value is selected using a heuristic. + */ + uint32_t vq_n_centers; + /** The number of iterations searching for kmeans centers (both VQ & PQ phases). */ + uint32_t kmeans_n_iters; + /** + * The fraction of data to use during iterative kmeans building (VQ phase). + * When zero, an optimal value is selected using a heuristic. + */ + double vq_kmeans_trainset_fraction; + /** + * The fraction of data to use during iterative kmeans building (PQ phase). + * When zero, an optimal value is selected using a heuristic. + */ + double pq_kmeans_trainset_fraction; +}; + +typedef struct cuvsCagraCompressionParams* cuvsCagraCompressionParams_t; + /** * @brief Supplemental parameters to build CAGRA Index * @@ -53,6 +92,12 @@ struct cuvsCagraIndexParams { enum cuvsCagraGraphBuildAlgo build_algo; /** Number of Iterations to run if building with NN_DESCENT */ size_t nn_descent_niter; + /** + * Optional: specify compression parameters if compression is desired. + * + * NOTE: this is experimental new API, consider it unsafe. + */ + cuvsCagraCompressionParams_t compression; }; typedef struct cuvsCagraIndexParams* cuvsCagraIndexParams_t; @@ -73,6 +118,22 @@ cuvsError_t cuvsCagraIndexParamsCreate(cuvsCagraIndexParams_t* params); */ cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t params); +/** + * @brief Allocate CAGRA Compression params, and populate with default values + * + * @param[in] params cuvsCagraCompressionParams_t to allocate + * @return cuvsError_t + */ +cuvsError_t cuvsCagraCompressionParamsCreate(cuvsCagraCompressionParams_t* params); + +/** + * @brief De-allocate CAGRA Compression params + * + * @param[in] params + * @return cuvsError_t + */ +cuvsError_t cuvsCagraCompressionParamsDestroy(cuvsCagraCompressionParams_t params); + /** * @} */ diff --git a/cpp/include/cuvs/neighbors/cagra.hpp b/cpp/include/cuvs/neighbors/cagra.hpp index 0f2919ff46..d6f5fef496 100644 --- a/cpp/include/cuvs/neighbors/cagra.hpp +++ b/cpp/include/cuvs/neighbors/cagra.hpp @@ -25,6 +25,8 @@ #include #include +#include + namespace cuvs::neighbors::cagra { /** * @defgroup cagra_cpp_index_params CAGRA index build parameters @@ -42,6 +44,54 @@ enum class graph_build_algo { NN_DESCENT }; +/** Parameters for VPQ compression. */ +struct vpq_params { + /** + * The bit length of the vector element after compression by PQ. + * + * Possible values: [4, 5, 6, 7, 8]. + * + * Hint: the smaller the 'pq_bits', the smaller the index size and the better the search + * performance, but the lower the recall. + */ + uint32_t pq_bits = 8; + /** + * The dimensionality of the vector after compression by PQ. + * When zero, an optimal value is selected using a heuristic. + * + * TODO: at the moment `dim` must be a multiple `pq_dim`. + */ + uint32_t pq_dim = 0; + /** + * Vector Quantization (VQ) codebook size - number of "coarse cluster centers". + * When zero, an optimal value is selected using a heuristic. + */ + uint32_t vq_n_centers = 0; + /** The number of iterations searching for kmeans centers (both VQ & PQ phases). */ + uint32_t kmeans_n_iters = 25; + /** + * The fraction of data to use during iterative kmeans building (VQ phase). + * When zero, an optimal value is selected using a heuristic. + */ + double vq_kmeans_trainset_fraction = 0; + /** + * The fraction of data to use during iterative kmeans building (PQ phase). + * When zero, an optimal value is selected using a heuristic. + */ + double pq_kmeans_trainset_fraction = 0; + + /** Build a raft CAGRA index params from an existing cuvs CAGRA index params. */ + operator raft::neighbors::vpq_params() const + { + return {.pq_bits = pq_bits, + .pq_dim = pq_dim, + .vq_n_centers = vq_n_centers, + .kmeans_n_iters = kmeans_n_iters, + .vq_kmeans_trainset_fraction = vq_kmeans_trainset_fraction, + .pq_kmeans_trainset_fraction = pq_kmeans_trainset_fraction}; + } +}; + struct index_params : ann::index_params { /** Degree of input graph for pruning. */ size_t intermediate_graph_degree = 128; @@ -51,6 +101,12 @@ struct index_params : ann::index_params { graph_build_algo build_algo = graph_build_algo::IVF_PQ; /** Number of Iterations to run if building with NN_DESCENT */ size_t nn_descent_niter = 20; + /** + * Specify compression parameters if compression is desired. + * + * NOTE: this is experimental new API, consider it unsafe. + */ + std::optional compression = std::nullopt; /** Build a raft CAGRA index params from an existing cuvs CAGRA index params. */ operator raft::neighbors::cagra::index_params() const @@ -64,7 +120,8 @@ struct index_params : ann::index_params { .intermediate_graph_degree = intermediate_graph_degree, .graph_degree = graph_degree, .build_algo = static_cast((int)build_algo), - .nn_descent_niter = nn_descent_niter}; + .nn_descent_niter = nn_descent_niter, + .compression = compression}; } }; diff --git a/cpp/src/neighbors/cagra_c.cpp b/cpp/src/neighbors/cagra_c.cpp index 9e50870163..2e96e8db68 100644 --- a/cpp/src/neighbors/cagra_c.cpp +++ b/cpp/src/neighbors/cagra_c.cpp @@ -45,6 +45,17 @@ void* _build(cuvsResources_t res, cuvsCagraIndexParams params, DLManagedTensor* static_cast(params.build_algo); build_params.nn_descent_niter = params.nn_descent_niter; + if (auto* cparams = params.compression; cparams != nullptr) { + auto compression_params = cuvs::neighbors::cagra::vpq_params(); + compression_params.pq_bits = cparams->pq_bits; + compression_params.pq_dim = cparams->pq_dim; + compression_params.vq_n_centers = cparams->vq_n_centers; + compression_params.kmeans_n_iters = cparams->kmeans_n_iters; + compression_params.vq_kmeans_trainset_fraction = cparams->vq_kmeans_trainset_fraction; + compression_params.pq_kmeans_trainset_fraction = cparams->pq_kmeans_trainset_fraction; + build_params.compression.emplace(compression_params); + } + if (cuvs::core::is_dlpack_device_compatible(dataset)) { using mdspan_type = raft::device_matrix_view; auto mds = cuvs::core::from_dlpack(dataset_tensor); @@ -129,7 +140,6 @@ extern "C" cuvsError_t cuvsCagraBuild(cuvsResources_t res, { return cuvs::core::translate_exceptions([=] { auto dataset = dataset_tensor->dl_tensor; - if (dataset.dtype.code == kDLFloat && dataset.dtype.bits == 32) { index->addr = reinterpret_cast(_build(res, *params, dataset_tensor)); index->dtype.code = kDLFloat; @@ -203,6 +213,25 @@ extern "C" cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t params return cuvs::core::translate_exceptions([=] { delete params; }); } +extern "C" cuvsError_t cuvsCagraCompressionParamsCreate(cuvsCagraCompressionParams_t* params) +{ + return cuvs::core::translate_exceptions([=] { + auto ps = cuvs::neighbors::cagra::vpq_params(); + *params = + new cuvsCagraCompressionParams{.pq_bits = ps.pq_bits, + .pq_dim = ps.pq_dim, + .vq_n_centers = ps.vq_n_centers, + .kmeans_n_iters = ps.kmeans_n_iters, + .vq_kmeans_trainset_fraction = ps.vq_kmeans_trainset_fraction, + .pq_kmeans_trainset_fraction = ps.pq_kmeans_trainset_fraction}; + }); +} + +extern "C" cuvsError_t cuvsCagraCompressionParamsDestroy(cuvsCagraCompressionParams_t params) +{ + return cuvs::core::translate_exceptions([=] { delete params; }); +} + extern "C" cuvsError_t cuvsCagraSearchParamsCreate(cuvsCagraSearchParams_t* params) { return cuvs::core::translate_exceptions([=] { diff --git a/cpp/test/neighbors/ann_cagra.cuh b/cpp/test/neighbors/ann_cagra.cuh index 9c58275c60..c9bc0808d2 100644 --- a/cpp/test/neighbors/ann_cagra.cuh +++ b/cpp/test/neighbors/ann_cagra.cuh @@ -39,6 +39,7 @@ #include #include +#include #include #include @@ -144,6 +145,7 @@ struct AnnCagraInputs { bool include_serialized_dataset; // std::optional double min_recall; // = std::nullopt; + std::optional compression = std::nullopt; }; inline ::std::ostream& operator<<(::std::ostream& os, const AnnCagraInputs& p) @@ -154,7 +156,13 @@ inline ::std::ostream& operator<<(::std::ostream& os, const AnnCagraInputs& p) << ", k=" << p.k << ", " << algo.at((int)p.algo) << ", max_queries=" << p.max_queries << ", itopk_size=" << p.itopk_size << ", search_width=" << p.search_width << ", metric=" << static_cast(p.metric) << (p.host_dataset ? ", host" : ", device") - << ", build_algo=" << build_algo.at((int)p.build_algo) << '}' << std::endl; + << ", build_algo=" << build_algo.at((int)p.build_algo); + if (p.compression.has_value()) { + auto vpq = p.compression.value(); + os << ", pq_bits=" << vpq.pq_bits << ", pq_dim=" << vpq.pq_dim + << ", vq_n_centers=" << vpq.vq_n_centers; + } + os << '}' << std::endl; return os; } @@ -204,7 +212,8 @@ class AnnCagraTest : public ::testing::TestWithParam { cagra::index_params index_params; index_params.metric = ps.metric; // Note: currently ony the cagra::index_params metric is // not used for knn_graph building. - index_params.build_algo = ps.build_algo; + index_params.build_algo = ps.build_algo; + index_params.compression = ps.compression; cagra::search_params search_params; search_params.algo = ps.algo; search_params.max_queries = ps.max_queries; @@ -261,17 +270,20 @@ class AnnCagraTest : public ::testing::TestWithParam { ps.k, 0.003, min_recall)); - EXPECT_TRUE(eval_distances(handle_, - database.data(), - search_queries.data(), - indices_dev.data(), - distances_dev.data(), - ps.n_rows, - ps.dim, - ps.n_queries, - ps.k, - ps.metric, - 1.0e-4)); + if (!ps.compression.has_value()) { + // Don't evaluate distances for CAGRA-Q for now as the error can be somewhat large + EXPECT_TRUE(eval_distances(handle_, + database.data(), + search_queries.data(), + indices_dev.data(), + distances_dev.data(), + ps.n_rows, + ps.dim, + ps.n_queries, + ps.k, + ps.metric, + 1.0e-4)); + } } } @@ -394,6 +406,34 @@ inline std::vector generate_inputs() {0.995}); inputs.insert(inputs.end(), inputs2.begin(), inputs2.end()); + // a few PQ configurations + inputs2 = raft::util::itertools::product( + {100}, + {10000}, + {64, 128, 192, 256, 512, 1024}, // dim + {16}, // k + {graph_build_algo::IVF_PQ}, + {search_algo::AUTO}, + {10}, + {0}, + {64}, + {1}, + {cuvs::distance::DistanceType::L2Expanded}, + {false}, + {true}, + {0.6}); // don't demand high recall without refinement + for (uint32_t pq_len : {2}) { // for now, only pq_len = 2 is supported, more options coming soon + for (uint32_t vq_n_centers : {100, 1000}) { + for (auto input : inputs2) { + vpq_params ps{}; + ps.pq_dim = input.dim / pq_len; + ps.vq_n_centers = vq_n_centers; + input.compression.emplace(ps); + inputs.push_back(input); + } + } + } + return inputs; } From d62e1ce574077d08d5572d34c13fa7743bb57dad Mon Sep 17 00:00:00 2001 From: Divye Gala Date: Tue, 2 Apr 2024 18:55:28 -0400 Subject: [PATCH 026/623] CAGRA C example and DlPack docs (#51) Authors: - Divye Gala (https://github.com/divyegala) - Corey J. Nolet (https://github.com/cjnolet) - Kyle Edwards (https://github.com/KyleFromNVIDIA) - Bradley Dice (https://github.com/bdice) - Ben Frederickson (https://github.com/benfred) Approvers: - Dante Gama Dessavre (https://github.com/dantegd) - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/51 --- build.sh | 2 +- docs/source/interoperability.rst | 34 ++++ examples/{cpp => }/README.md | 6 +- examples/build.sh | 55 +++++++ examples/c/CMakeLists.txt | 37 +++++ examples/c/src/cagra_c_example.c | 148 ++++++++++++++++++ .../cmake/thirdparty/fetch_rapids.cmake | 0 .../{cpp => }/cmake/thirdparty/get_cuvs.cmake | 14 +- examples/cmake/thirdparty/get_dlpack.cmake | 41 +++++ examples/cpp/CMakeLists.txt | 8 +- examples/cpp/build.sh | 41 ----- examples/cpp/src/common.cuh | 67 ++++---- 12 files changed, 372 insertions(+), 81 deletions(-) rename examples/{cpp => }/README.md (73%) create mode 100755 examples/build.sh create mode 100644 examples/c/CMakeLists.txt create mode 100644 examples/c/src/cagra_c_example.c rename examples/{cpp => }/cmake/thirdparty/fetch_rapids.cmake (100%) rename examples/{cpp => }/cmake/thirdparty/get_cuvs.cmake (79%) create mode 100644 examples/cmake/thirdparty/get_dlpack.cmake delete mode 100755 examples/cpp/build.sh diff --git a/build.sh b/build.sh index 0c987ed962..822b845904 100755 --- a/build.sh +++ b/build.sh @@ -419,7 +419,7 @@ fi # Initiate build for c++ examples (if needed) if hasArg examples; then - pushd ${REPODIR}/examples/cpp + pushd ${REPODIR}/examples ./build.sh popd fi diff --git a/docs/source/interoperability.rst b/docs/source/interoperability.rst index fc8d19ef43..4dfe1e09af 100644 --- a/docs/source/interoperability.rst +++ b/docs/source/interoperability.rst @@ -4,6 +4,40 @@ Interoperability DLPack (C) ^^^^^^^^^^ +Approximate nearest neighbor (ANN) indexes provide an interface to build and search an index via a C API. [DLPack v0.8](https://github.com/dmlc/dlpack/blob/main/README.md), a tensor interface framework, is used as the standard to interact with our C API. + +Representing a tensor with DLPack is simple, as it is a POD struct that stores information about the tensor at runtime. At the moment, `DLManagedTensor` from DLPack v0.8 is compatible with out C API however we will soon upgrade to `DLManagedTensorVersioned` from DLPack v1.0 as it will help us maintain ABI and API compatibility. + +Here's an example on how to represent device memory using `DLManagedTensor`: + +.. code-block:: c + + #include + + // Create data representation in host memory + float dataset[2][1] = {{0.2, 0.1}}; + // copy data to device memory + float *dataset_dev; + cuvsRMMAlloc(&dataset_dev, sizeof(float) * 2 * 1); + cudaMemcpy(dataset_dev, dataset, sizeof(float) * 2 * 1, cudaMemcpyDefault); + + // Use DLPack for representing the data as a tensor + DLManagedTensor dataset_tensor; + dataset_tensor.dl_tensor.data = dataset; + dataset_tensor.dl_tensor.device.device_type = kDLCUDA; + dataset_tensor.dl_tensor.ndim = 2; + dataset_tensor.dl_tensor.dtype.code = kDLFloat; + dataset_tensor.dl_tensor.dtype.bits = 32; + dataset_tensor.dl_tensor.dtype.lanes = 1; + int64_t dataset_shape[2] = {2, 1}; + dataset_tensor.dl_tensor.shape = dataset_shape; + dataset_tensor.dl_tensor.strides = nullptr; + + // free memory after use + cuvsRMMFree(dataset_dev); + +Please refer to cuVS C API `documentation `_ to learn more. + Multi-dimensional span (C++) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/cpp/README.md b/examples/README.md similarity index 73% rename from examples/cpp/README.md rename to examples/README.md index 5a98dd3904..701fd0deee 100644 --- a/examples/cpp/README.md +++ b/examples/README.md @@ -1,4 +1,4 @@ -# cuVS C++ Examples +# cuVS C and C++ Examples This template project provides a drop-in sample to either start building a new application with, or using CUVS in an existing CMake project. @@ -6,11 +6,11 @@ First, please refer to our [installation docs](https://docs.rapids.ai/api/cuvs/s Once the minimum requirements are satisfied, this example template application can be built with the provided `build.sh` script. This is a bash script that calls the appropriate CMake commands, so you can look into it to see the typical CMake based build workflow. -This directory (`CUVS_SOURCE/examples/cpp`) can be copied directly in order to build a new application with CUVS. +The directories (`CUVS_SOURCE/examples/c`) or (`CUVS_SOURCE/examples/cpp`) can be copied directly in order to build a new application with cuVS. cuVS can be integrated into an existing CMake project by copying the contents in the `configure rapids-cmake` and `configure cuvs` sections of the provided `CMakeLists.txt` into your project, along with `cmake/thirdparty/get_cuvs.cmake`. -Make sure to link against the appropriate Cmake targets. Use `cuvs::cuvs` to utilize the shared library. +Make sure to link against the appropriate CMake targets. Use `cuvs::c_api` and `cuvs::cuvs` to use the C and C++ shared libraries respectively. ```cmake target_link_libraries(your_app_target PRIVATE cuvs::cuvs) diff --git a/examples/build.sh b/examples/build.sh new file mode 100755 index 0000000000..cd021ca21d --- /dev/null +++ b/examples/build.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Copyright (c) 2023-2024, NVIDIA CORPORATION. + +# cuvs empty project template build script + +# Abort script on first error +set -e + +PARALLEL_LEVEL=${PARALLEL_LEVEL:=`nproc`} + +BUILD_TYPE=Release +BUILD_DIR=build/ + +CUVS_REPO_REL="" +EXTRA_CMAKE_ARGS="" +set -e + +# Root of examples +EXAMPLES_DIR=$(dirname "$(realpath "$0")") + +if [[ ${CUVS_REPO_REL} != "" ]]; then + CUVS_REPO_PATH="`readlink -f \"${CUVS_REPO_REL}\"`" + EXTRA_CMAKE_ARGS="${EXTRA_CMAKE_ARGS} -DCPM_cuvs_SOURCE=${CUVS_REPO_PATH}" +else + LIB_BUILD_DIR=${LIB_BUILD_DIR:-$(readlink -f "${EXAMPLES_DIR}/../cpp/build")} + EXTRA_CMAKE_ARGS="${EXTRA_CMAKE_ARGS} -Dcuvs_ROOT=${LIB_BUILD_DIR}" +fi + +if [ "$1" == "clean" ]; then + rm -rf build + exit 0 +fi + +################################################################################ +# Add individual libcudf examples build scripts down below + +build_example() { + example_dir=${1} + example_dir="${EXAMPLES_DIR}/${example_dir}" + build_dir="${example_dir}/build" + + # Configure + cmake -S ${example_dir} -B ${build_dir} \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -DCUVS_NVTX=OFF \ + -DCMAKE_CUDA_ARCHITECTURES="native" \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + ${EXTRA_CMAKE_ARGS} + # Build + cmake --build ${build_dir} -j${PARALLEL_LEVEL} +} + +build_example c +build_example cpp diff --git a/examples/c/CMakeLists.txt b/examples/c/CMakeLists.txt new file mode 100644 index 0000000000..fb508728f0 --- /dev/null +++ b/examples/c/CMakeLists.txt @@ -0,0 +1,37 @@ +# ============================================================================= +# Copyright (c) 2023-2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. + +cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) + +# ------------- configure rapids-cmake --------------# + +include(../cmake/thirdparty/fetch_rapids.cmake) +include(rapids-cmake) +include(rapids-cpm) +include(rapids-export) +include(rapids-find) + +# ------------- configure project --------------# + +project(test_cuvs_c LANGUAGES C CXX CUDA) + +# ------------- configure cuvs -----------------# + +rapids_cpm_init() +set(BUILD_CUVS_C_LIBRARY ON) +include(../cmake/thirdparty/get_dlpack.cmake) +include(../cmake/thirdparty/get_cuvs.cmake) + +add_executable(CAGRA_C_EXAMPLE src/cagra_c_example.c) +target_include_directories(CAGRA_C_EXAMPLE PUBLIC "$") +target_link_libraries(CAGRA_C_EXAMPLE PRIVATE cuvs::c_api $) diff --git a/examples/c/src/cagra_c_example.c b/examples/c/src/cagra_c_example.c new file mode 100644 index 0000000000..fdcbbf5712 --- /dev/null +++ b/examples/c/src/cagra_c_example.c @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2022-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include +#include +#include + +float dataset[4][2] = {{0.74021935, 0.9209938}, + {0.03902049, 0.9689629}, + {0.92514056, 0.4463501}, + {0.6673192, 0.10993068}}; +float queries[4][2] = {{0.48216683, 0.0428398}, + {0.5084142, 0.6545497}, + {0.51260436, 0.2643005}, + {0.05198065, 0.5789965}}; + +void cagra_build_search_simple() { + + int64_t n_rows = 4; + int64_t n_cols = 2; + int64_t topk = 2; + int64_t n_queries = 4; + + // Create a cuvsResources_t object + cuvsResources_t res; + cuvsResourcesCreate(&res); + + // Use DLPack to represent `dataset` as a tensor + DLManagedTensor dataset_tensor; + dataset_tensor.dl_tensor.data = dataset; + dataset_tensor.dl_tensor.device.device_type = kDLCPU; + dataset_tensor.dl_tensor.ndim = 2; + dataset_tensor.dl_tensor.dtype.code = kDLFloat; + dataset_tensor.dl_tensor.dtype.bits = 32; + dataset_tensor.dl_tensor.dtype.lanes = 1; + int64_t dataset_shape[2] = {n_rows, n_cols}; + dataset_tensor.dl_tensor.shape = dataset_shape; + dataset_tensor.dl_tensor.strides = NULL; + + // Build the CAGRA index + cuvsCagraIndexParams_t index_params; + cuvsCagraIndexParamsCreate(&index_params); + + cuvsCagraIndex_t index; + cuvsCagraIndexCreate(&index); + + cuvsCagraBuild(res, index_params, &dataset_tensor, index); + + // Allocate memory for `queries`, `neighbors` and `distances` output + uint32_t *neighbors; + float *distances, *queries_d; + cuvsRMMAlloc(res, (void**) &queries_d, sizeof(float) * n_queries * n_cols); + cuvsRMMAlloc(res, (void**) &neighbors, sizeof(uint32_t) * n_queries * topk); + cuvsRMMAlloc(res, (void**) &distances, sizeof(float) * n_queries * topk); + + // Use DLPack to represent `queries`, `neighbors` and `distances` as tensors + cudaMemcpy(queries_d, queries, sizeof(float) * 4 * 2, cudaMemcpyDefault); + + DLManagedTensor queries_tensor; + queries_tensor.dl_tensor.data = queries_d; + queries_tensor.dl_tensor.device.device_type = kDLCUDA; + queries_tensor.dl_tensor.ndim = 2; + queries_tensor.dl_tensor.dtype.code = kDLFloat; + queries_tensor.dl_tensor.dtype.bits = 32; + queries_tensor.dl_tensor.dtype.lanes = 1; + int64_t queries_shape[2] = {n_queries, n_cols}; + queries_tensor.dl_tensor.shape = queries_shape; + queries_tensor.dl_tensor.strides = NULL; + + DLManagedTensor neighbors_tensor; + neighbors_tensor.dl_tensor.data = neighbors; + neighbors_tensor.dl_tensor.device.device_type = kDLCUDA; + neighbors_tensor.dl_tensor.ndim = 2; + neighbors_tensor.dl_tensor.dtype.code = kDLUInt; + neighbors_tensor.dl_tensor.dtype.bits = 32; + neighbors_tensor.dl_tensor.dtype.lanes = 1; + int64_t neighbors_shape[2] = {n_queries, topk}; + neighbors_tensor.dl_tensor.shape = neighbors_shape; + neighbors_tensor.dl_tensor.strides = NULL; + + DLManagedTensor distances_tensor; + distances_tensor.dl_tensor.data = distances; + distances_tensor.dl_tensor.device.device_type = kDLCUDA; + distances_tensor.dl_tensor.ndim = 2; + distances_tensor.dl_tensor.dtype.code = kDLFloat; + distances_tensor.dl_tensor.dtype.bits = 32; + distances_tensor.dl_tensor.dtype.lanes = 1; + int64_t distances_shape[2] = {n_queries, topk}; + distances_tensor.dl_tensor.shape = distances_shape; + distances_tensor.dl_tensor.strides = NULL; + + // Search the CAGRA index + cuvsCagraSearchParams_t search_params; + cuvsCagraSearchParamsCreate(&search_params); + + cuvsCagraSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, + &distances_tensor); + + // print results + uint32_t *neighbors_h = + (uint32_t *)malloc(sizeof(uint32_t) * n_queries * topk); + float *distances_h = (float *)malloc(sizeof(float) * n_queries * topk); + cudaMemcpy(neighbors_h, neighbors, sizeof(uint32_t) * n_queries * topk, + cudaMemcpyDefault); + cudaMemcpy(distances_h, distances, sizeof(float) * n_queries * topk, + cudaMemcpyDefault); + printf("Query 0 neighbor indices: =[%d, %d]\n", neighbors_h[0], + neighbors_h[1]); + printf("Query 0 neighbor distances: =[%f, %f]\n", distances_h[0], + distances_h[1]); + + // Free or destroy all allocations + free(neighbors_h); + free(distances_h); + + cuvsCagraSearchParamsDestroy(search_params); + + cuvsRMMFree(res, distances, sizeof(float) * n_queries * topk); + cuvsRMMFree(res, neighbors, sizeof(uint32_t) * n_queries * topk); + cuvsRMMFree(res, queries_d, sizeof(float) * n_queries * n_cols); + + cuvsCagraIndexDestroy(index); + cuvsCagraIndexParamsDestroy(index_params); + cuvsResourcesDestroy(res); +} + +int main() { + // Simple build and search example. + cagra_build_search_simple(); +} diff --git a/examples/cpp/cmake/thirdparty/fetch_rapids.cmake b/examples/cmake/thirdparty/fetch_rapids.cmake similarity index 100% rename from examples/cpp/cmake/thirdparty/fetch_rapids.cmake rename to examples/cmake/thirdparty/fetch_rapids.cmake diff --git a/examples/cpp/cmake/thirdparty/get_cuvs.cmake b/examples/cmake/thirdparty/get_cuvs.cmake similarity index 79% rename from examples/cpp/cmake/thirdparty/get_cuvs.cmake rename to examples/cmake/thirdparty/get_cuvs.cmake index 0b3821e207..a2a4e88420 100644 --- a/examples/cpp/cmake/thirdparty/get_cuvs.cmake +++ b/examples/cmake/thirdparty/get_cuvs.cmake @@ -17,22 +17,29 @@ set(CUVS_FORK "rapidsai") set(CUVS_PINNED_TAG "branch-${RAPIDS_VERSION}") function(find_and_configure_cuvs) - set(oneValueArgs VERSION FORK PINNED_TAG ENABLE_NVTX) + set(oneValueArgs VERSION FORK PINNED_TAG ENABLE_NVTX BUILD_CUVS_C_LIBRARY) cmake_parse_arguments(PKG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + + set(CUVS_COMPONENTS "") + if(PKG_BUILD_CUVS_C_LIBRARY) + string(APPEND CUVS_COMPONENTS " c_api") + endif() #----------------------------------------------------- # Invoke CPM find_package() #----------------------------------------------------- rapids_cpm_find(cuvs ${PKG_VERSION} GLOBAL_TARGETS cuvs::cuvs - BUILD_EXPORT_SET cuvs-template-exports - INSTALL_EXPORT_SET cuvs-template-exports + BUILD_EXPORT_SET cuvs-examples-exports + INSTALL_EXPORT_SET cuvs-examples-exports + COMPONENTS ${CUVS_COMPONENTS} CPM_ARGS GIT_REPOSITORY https://github.com/${PKG_FORK}/cuvs.git GIT_TAG ${PKG_PINNED_TAG} SOURCE_SUBDIR cpp OPTIONS + "BUILD_C_LIBRARY ${PKG_BUILD_CUVS_C_LIBRARY}" "BUILD_TESTS OFF" "CUVS_NVTX ${PKG_ENABLE_NVTX}" ) @@ -45,4 +52,5 @@ find_and_configure_cuvs(VERSION ${CUVS_VERSION}.00 FORK ${CUVS_FORK} PINNED_TAG ${CUVS_PINNED_TAG} ENABLE_NVTX OFF + BUILD_CUVS_C_LIBRARY ${BUILD_CUVS_C_LIBRARY} ) diff --git a/examples/cmake/thirdparty/get_dlpack.cmake b/examples/cmake/thirdparty/get_dlpack.cmake new file mode 100644 index 0000000000..7c90cb3d23 --- /dev/null +++ b/examples/cmake/thirdparty/get_dlpack.cmake @@ -0,0 +1,41 @@ +# ============================================================================= +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +# This function finds dlpack and sets any additional necessary environment variables. +function(find_and_configure_dlpack VERSION) + + include(${rapids-cmake-dir}/find/generate_module.cmake) + rapids_find_generate_module(DLPACK HEADER_NAMES dlpack.h) + + rapids_cpm_find( + dlpack ${VERSION} + GIT_REPOSITORY https://github.com/dmlc/dlpack.git + GIT_TAG v${VERSION} + GIT_SHALLOW TRUE + DOWNLOAD_ONLY TRUE + OPTIONS "BUILD_MOCK OFF" + ) + + if(DEFINED dlpack_SOURCE_DIR) + # otherwise find_package(DLPACK) will set this variable + set(DLPACK_INCLUDE_DIR + "${dlpack_SOURCE_DIR}/include" + PARENT_SCOPE + ) + endif() +endfunction() + +set(CUVS_MIN_VERSION_dlpack 0.8) + +find_and_configure_dlpack(${CUVS_MIN_VERSION_dlpack}) diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index dc8a70a5ff..5ff7bef612 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -15,7 +15,7 @@ cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) # ------------- configure rapids-cmake --------------# -include(cmake/thirdparty/fetch_rapids.cmake) +include(../cmake/thirdparty/fetch_rapids.cmake) include(rapids-cmake) include(rapids-cpm) include(rapids-cuda) @@ -31,8 +31,12 @@ project(test_cuvs LANGUAGES CXX CUDA) # ------------- configure cuvs -----------------# rapids_cpm_init() -include(cmake/thirdparty/get_cuvs.cmake) +set(BUILD_CUVS_C_LIBRARY OFF) +include(../cmake/thirdparty/get_cuvs.cmake) # -------------- compile tasks ----------------- # add_executable(CAGRA_EXAMPLE src/cagra_example.cu) + +# `$` is a generator expression that ensures that targets +# are installed in a conda environment, if one exists target_link_libraries(CAGRA_EXAMPLE PRIVATE cuvs::cuvs $) diff --git a/examples/cpp/build.sh b/examples/cpp/build.sh deleted file mode 100755 index 7a948d9a8a..0000000000 --- a/examples/cpp/build.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2023-2024, NVIDIA CORPORATION. - -# cuvs empty project template build script - -# Abort script on first error -set -e - -PARALLEL_LEVEL=${PARALLEL_LEVEL:=`nproc`} - -BUILD_TYPE=Release -BUILD_DIR=build/ - -CUVS_REPO_REL="" -EXTRA_CMAKE_ARGS="" -set -e - - -if [[ ${CUVS_REPO_REL} != "" ]]; then - CUVS_REPO_PATH="`readlink -f \"${CUVS_REPO_REL}\"`" - EXTRA_CMAKE_ARGS="${EXTRA_CMAKE_ARGS} -DCPM_cuvs_SOURCE=${CUVS_REPO_PATH}" -fi - -if [ "$1" == "clean" ]; then - rm -rf build - exit 0 -fi - -mkdir -p $BUILD_DIR -cd $BUILD_DIR - -cmake \ - -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ - -DCUVS_NVTX=OFF \ - -DCMAKE_CUDA_ARCHITECTURES="NATIVE" \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - ${EXTRA_CMAKE_ARGS} \ - ../ - -cmake --build . -j${PARALLEL_LEVEL} diff --git a/examples/cpp/src/common.cuh b/examples/cpp/src/common.cuh index 757123cea5..aade6d4afe 100644 --- a/examples/cpp/src/common.cuh +++ b/examples/cpp/src/common.cuh @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -28,34 +29,36 @@ #include // Fill dataset and queries with synthetic data. -void generate_dataset(raft::device_resources const& dev_resources, +void generate_dataset(raft::device_resources const &dev_resources, raft::device_matrix_view dataset, - raft::device_matrix_view queries) -{ - auto labels = raft::make_device_vector(dev_resources, dataset.extent(0)); + raft::device_matrix_view queries) { + auto labels = raft::make_device_vector(dev_resources, + dataset.extent(0)); raft::random::make_blobs(dev_resources, dataset, labels.view()); raft::random::RngState r(1234ULL); - raft::random::uniform(dev_resources, - r, - raft::make_device_vector_view(queries.data_handle(), queries.size()), - -1.0f, - 1.0f); + raft::random::uniform( + dev_resources, r, + raft::make_device_vector_view(queries.data_handle(), queries.size()), + -1.0f, 1.0f); } // Copy the results to host and print a few samples template -void print_results(raft::device_resources const& dev_resources, +void print_results(raft::device_resources const &dev_resources, raft::device_matrix_view neighbors, - raft::device_matrix_view distances) -{ - int64_t topk = neighbors.extent(1); - auto neighbors_host = raft::make_host_matrix(neighbors.extent(0), topk); - auto distances_host = raft::make_host_matrix(distances.extent(0), topk); + raft::device_matrix_view distances) { + int64_t topk = neighbors.extent(1); + auto neighbors_host = + raft::make_host_matrix(neighbors.extent(0), topk); + auto distances_host = + raft::make_host_matrix(distances.extent(0), topk); cudaStream_t stream = raft::resource::get_cuda_stream(dev_resources); - raft::copy(neighbors_host.data_handle(), neighbors.data_handle(), neighbors.size(), stream); - raft::copy(distances_host.data_handle(), distances.data_handle(), distances.size(), stream); + raft::copy(neighbors_host.data_handle(), neighbors.data_handle(), + neighbors.size(), stream); + raft::copy(distances_host.data_handle(), distances.data_handle(), + distances.size(), stream); // The calls to RAFT algorithms and raft::copy is asynchronous. // We need to sync the stream before accessing the data. @@ -70,26 +73,28 @@ void print_results(raft::device_resources const& dev_resources, } /** Subsample the dataset to create a training set*/ -raft::device_matrix subsample( - raft::device_resources const& dev_resources, - raft::device_matrix_view dataset, - raft::device_vector_view data_indices, - float fraction) -{ +raft::device_matrix +subsample(raft::device_resources const &dev_resources, + raft::device_matrix_view dataset, + raft::device_vector_view data_indices, + float fraction) { int64_t n_samples = dataset.extent(0); - int64_t n_dim = dataset.extent(1); - int64_t n_train = n_samples * fraction; - auto trainset = raft::make_device_matrix(dev_resources, n_train, n_dim); + int64_t n_dim = dataset.extent(1); + int64_t n_train = n_samples * fraction; + auto trainset = + raft::make_device_matrix(dev_resources, n_train, n_dim); int seed = 137; raft::random::RngState rng(seed); - auto train_indices = raft::make_device_vector(dev_resources, n_train); + auto train_indices = + raft::make_device_vector(dev_resources, n_train); - raft::random::sample_without_replacement( - dev_resources, rng, data_indices, std::nullopt, train_indices.view(), std::nullopt); + raft::random::sample_without_replacement(dev_resources, rng, data_indices, + std::nullopt, train_indices.view(), + std::nullopt); - raft::matrix::copy_rows( - dev_resources, dataset, trainset.view(), raft::make_const_mdspan(train_indices.view())); + raft::matrix::copy_rows(dev_resources, dataset, trainset.view(), + raft::make_const_mdspan(train_indices.view())); return trainset; } From d23de1b842aab059c17df37188b3b6b21a5caf4c Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Thu, 4 Apr 2024 09:41:05 -0700 Subject: [PATCH 027/623] Add Brute Force KNN to Python and Rust API's (#59) Authors: - Ben Frederickson (https://github.com/benfred) - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/59 --- cpp/include/cuvs/distance/distance_types.h | 3 +- cpp/include/cuvs/neighbors/brute_force.h | 40 ++-- cpp/src/neighbors/brute_force_c.cpp | 56 ++--- cpp/src/neighbors/ivf_flat_c.cpp | 59 ++--- cpp/test/neighbors/run_brute_force_c.c | 8 +- python/cuvs/cuvs/distance_type.pxd | 41 ++++ python/cuvs/cuvs/neighbors/CMakeLists.txt | 1 + .../cuvs/neighbors/brute_force/CMakeLists.txt | 25 ++ .../cuvs/neighbors/brute_force/__init__.pxd | 0 .../cuvs/neighbors/brute_force/__init__.py | 18 ++ .../neighbors/brute_force/brute_force.pxd | 47 ++++ .../neighbors/brute_force/brute_force.pyx | 216 ++++++++++++++++++ python/cuvs/cuvs/test/test_brute_force.py | 90 ++++++++ rust/cuvs-sys/build.rs | 6 +- rust/cuvs-sys/cuvs_c_wrapper.h | 1 + rust/cuvs/src/brute_force.rs | 180 +++++++++++++++ rust/cuvs/src/distance_type.rs | 17 ++ rust/cuvs/src/dlpack.rs | 15 +- rust/cuvs/src/lib.rs | 3 +- 19 files changed, 715 insertions(+), 111 deletions(-) create mode 100644 python/cuvs/cuvs/distance_type.pxd create mode 100644 python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt create mode 100644 python/cuvs/cuvs/neighbors/brute_force/__init__.pxd create mode 100644 python/cuvs/cuvs/neighbors/brute_force/__init__.py create mode 100644 python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd create mode 100644 python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx create mode 100644 python/cuvs/cuvs/test/test_brute_force.py create mode 100644 rust/cuvs/src/brute_force.rs create mode 100644 rust/cuvs/src/distance_type.rs diff --git a/cpp/include/cuvs/distance/distance_types.h b/cpp/include/cuvs/distance/distance_types.h index 8e9a4149cb..6cc2a993b6 100644 --- a/cpp/include/cuvs/distance/distance_types.h +++ b/cpp/include/cuvs/distance/distance_types.h @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#pragma once #ifdef __cplusplus extern "C" { @@ -67,4 +68,4 @@ enum DistanceType { #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/cpp/include/cuvs/neighbors/brute_force.h b/cpp/include/cuvs/neighbors/brute_force.h index 0bb4d6bdbf..cb7ee2a30e 100644 --- a/cpp/include/cuvs/neighbors/brute_force.h +++ b/cpp/include/cuvs/neighbors/brute_force.h @@ -36,9 +36,9 @@ extern "C" { typedef struct { uintptr_t addr; DLDataType dtype; -} bruteForceIndex; +} cuvsBruteForceIndex; -typedef bruteForceIndex* cuvsBruteForceIndex_t; +typedef cuvsBruteForceIndex* cuvsBruteForceIndex_t; /** * @brief Allocate BRUTEFORCE index @@ -46,14 +46,14 @@ typedef bruteForceIndex* cuvsBruteForceIndex_t; * @param[in] index cuvsBruteForceIndex_t to allocate * @return cuvsError_t */ -cuvsError_t bruteForceIndexCreate(cuvsBruteForceIndex_t* index); +cuvsError_t cuvsBruteForceIndexCreate(cuvsBruteForceIndex_t* index); /** * @brief De-allocate BRUTEFORCE index * * @param[in] index cuvsBruteForceIndex_t to de-allocate */ -cuvsError_t bruteForceIndexDestroy(cuvsBruteForceIndex_t index); +cuvsError_t cuvsBruteForceIndexDestroy(cuvsBruteForceIndex_t index); /** * @} */ @@ -83,13 +83,13 @@ cuvsError_t bruteForceIndexDestroy(cuvsBruteForceIndex_t index); * * // Create BRUTEFORCE index * cuvsBruteForceIndex_t index; - * cuvsError_t index_create_status = bruteForceIndexCreate(&index); + * cuvsError_t index_create_status = cuvsBruteForceIndexCreate(&index); * * // Build the BRUTEFORCE Index - * cuvsError_t build_status = bruteForceBuild(res, &dataset_tensor, L2Expanded, 0.f, index); + * cuvsError_t build_status = cuvsBruteForceBuild(res, &dataset_tensor, L2Expanded, 0.f, index); * * // de-allocate `index` and `res` - * cuvsError_t index_destroy_status = bruteForceIndexDestroy(index); + * cuvsError_t index_destroy_status = cuvsBruteForceIndexDestroy(index); * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); * @endcode * @@ -100,11 +100,11 @@ cuvsError_t bruteForceIndexDestroy(cuvsBruteForceIndex_t index); * @param[out] index cuvsBruteForceIndex_t Newly built BRUTEFORCE index * @return cuvsError_t */ -cuvsError_t bruteForceBuild(cuvsResources_t res, - DLManagedTensor* dataset, - enum DistanceType metric, - float metric_arg, - cuvsBruteForceIndex_t index); +cuvsError_t cuvsBruteForceBuild(cuvsResources_t res, + DLManagedTensor* dataset, + enum DistanceType metric, + float metric_arg, + cuvsBruteForceIndex_t index); /** * @} */ @@ -136,24 +136,24 @@ cuvsError_t bruteForceBuild(cuvsResources_t res, * DLManagedTensor queries; * DLManagedTensor neighbors; * - * // Search the `index` built using `bruteForceBuild` - * cuvsError_t search_status = bruteForceSearch(res, index, &queries, &neighbors, &distances); + * // Search the `index` built using `cuvsBruteForceBuild` + * cuvsError_t search_status = cuvsBruteForceSearch(res, index, &queries, &neighbors, &distances); * * // de-allocate `res` * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); * @endcode * * @param[in] res cuvsResources_t opaque C handle - * @param[in] index bruteForceIndex which has been returned by `bruteForceBuild` + * @param[in] index cuvsBruteForceIndex which has been returned by `cuvsBruteForceBuild` * @param[in] queries DLManagedTensor* queries dataset to search * @param[out] neighbors DLManagedTensor* output `k` neighbors for queries * @param[out] distances DLManagedTensor* output `k` distances for queries */ -cuvsError_t bruteForceSearch(cuvsResources_t res, - cuvsBruteForceIndex_t index, - DLManagedTensor* queries, - DLManagedTensor* neighbors, - DLManagedTensor* distances); +cuvsError_t cuvsBruteForceSearch(cuvsResources_t res, + cuvsBruteForceIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances); /** * @} */ diff --git a/cpp/src/neighbors/brute_force_c.cpp b/cpp/src/neighbors/brute_force_c.cpp index 531fc9a57d..88349e089e 100644 --- a/cpp/src/neighbors/brute_force_c.cpp +++ b/cpp/src/neighbors/brute_force_c.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -49,7 +50,7 @@ void* _build(cuvsResources_t res, template void _search(cuvsResources_t res, - bruteForceIndex index, + cuvsBruteForceIndex index, DLManagedTensor* queries_tensor, DLManagedTensor* neighbors_tensor, DLManagedTensor* distances_tensor) @@ -70,19 +71,14 @@ void _search(cuvsResources_t res, } // namespace -extern "C" cuvsError_t bruteForceIndexCreate(cuvsBruteForceIndex_t* index) +extern "C" cuvsError_t cuvsBruteForceIndexCreate(cuvsBruteForceIndex_t* index) { - try { - *index = new bruteForceIndex{}; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions([=] { *index = new cuvsBruteForceIndex{}; }); } -extern "C" cuvsError_t bruteForceIndexDestroy(cuvsBruteForceIndex_t index_c_ptr) +extern "C" cuvsError_t cuvsBruteForceIndexDestroy(cuvsBruteForceIndex_t index_c_ptr) { - try { + return cuvs::core::translate_exceptions([=] { auto index = *index_c_ptr; if (index.dtype.code == kDLFloat) { @@ -96,19 +92,16 @@ extern "C" cuvsError_t bruteForceIndexDestroy(cuvsBruteForceIndex_t index_c_ptr) delete index_ptr; } delete index_c_ptr; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } -extern "C" cuvsError_t bruteForceBuild(cuvsResources_t res, - DLManagedTensor* dataset_tensor, - enum DistanceType metric, - float metric_arg, - cuvsBruteForceIndex_t index) +extern "C" cuvsError_t cuvsBruteForceBuild(cuvsResources_t res, + DLManagedTensor* dataset_tensor, + enum DistanceType metric, + float metric_arg, + cuvsBruteForceIndex_t index) { - try { + return cuvs::core::translate_exceptions([=] { auto dataset = dataset_tensor->dl_tensor; if (dataset.dtype.code == kDLFloat && dataset.dtype.bits == 32) { @@ -120,19 +113,16 @@ extern "C" cuvsError_t bruteForceBuild(cuvsResources_t res, dataset.dtype.code, dataset.dtype.bits); } - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } -extern "C" cuvsError_t bruteForceSearch(cuvsResources_t res, - cuvsBruteForceIndex_t index_c_ptr, - DLManagedTensor* queries_tensor, - DLManagedTensor* neighbors_tensor, - DLManagedTensor* distances_tensor) +extern "C" cuvsError_t cuvsBruteForceSearch(cuvsResources_t res, + cuvsBruteForceIndex_t index_c_ptr, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) { - try { + return cuvs::core::translate_exceptions([=] { auto queries = queries_tensor->dl_tensor; auto neighbors = neighbors_tensor->dl_tensor; auto distances = distances_tensor->dl_tensor; @@ -159,9 +149,5 @@ extern "C" cuvsError_t bruteForceSearch(cuvsResources_t res, queries.dtype.code, queries.dtype.bits); } - - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } diff --git a/cpp/src/neighbors/ivf_flat_c.cpp b/cpp/src/neighbors/ivf_flat_c.cpp index b9488ec366..d8185c4f77 100644 --- a/cpp/src/neighbors/ivf_flat_c.cpp +++ b/cpp/src/neighbors/ivf_flat_c.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -86,17 +87,12 @@ void _search(cuvsResources_t res, extern "C" cuvsError_t ivfFlatIndexCreate(cuvsIvfFlatIndex_t* index) { - try { - *index = new ivfFlatIndex{}; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions([=] { *index = new ivfFlatIndex{}; }); } extern "C" cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index_c_ptr) { - try { + return cuvs::core::translate_exceptions([=] { auto index = *index_c_ptr; if (index.dtype.code == kDLFloat) { @@ -113,10 +109,7 @@ extern "C" cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index_c_ptr) delete index_ptr; } delete index_c_ptr; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t ivfFlatBuild(cuvsResources_t res, @@ -124,7 +117,7 @@ extern "C" cuvsError_t ivfFlatBuild(cuvsResources_t res, DLManagedTensor* dataset_tensor, cuvsIvfFlatIndex_t index) { - try { + return cuvs::core::translate_exceptions([=] { auto dataset = dataset_tensor->dl_tensor; if (dataset.dtype.code == kDLFloat && dataset.dtype.bits == 32) { @@ -144,10 +137,7 @@ extern "C" cuvsError_t ivfFlatBuild(cuvsResources_t res, dataset.dtype.code, dataset.dtype.bits); } - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t ivfFlatSearch(cuvsResources_t res, @@ -157,7 +147,7 @@ extern "C" cuvsError_t ivfFlatSearch(cuvsResources_t res, DLManagedTensor* neighbors_tensor, DLManagedTensor* distances_tensor) { - try { + return cuvs::core::translate_exceptions([=] { auto queries = queries_tensor->dl_tensor; auto neighbors = neighbors_tensor->dl_tensor; auto distances = distances_tensor->dl_tensor; @@ -191,16 +181,12 @@ extern "C" cuvsError_t ivfFlatSearch(cuvsResources_t res, queries.dtype.code, queries.dtype.bits); } - - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t cuvsIvfFlatIndexParamsCreate(cuvsIvfFlatIndexParams_t* params) { - try { + return cuvs::core::translate_exceptions([=] { *params = new ivfFlatIndexParams{.metric = L2Expanded, .metric_arg = 2.0f, .add_data_on_build = true, @@ -209,38 +195,21 @@ extern "C" cuvsError_t cuvsIvfFlatIndexParamsCreate(cuvsIvfFlatIndexParams_t* pa .kmeans_trainset_fraction = 0.5, .adaptive_centers = false, .conservative_memory_allocation = false}; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + }); } extern "C" cuvsError_t cuvsIvfFlatIndexParamsDestroy(cuvsIvfFlatIndexParams_t params) { - try { - delete params; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions([=] { delete params; }); } extern "C" cuvsError_t cuvsIvfFlatSearchParamsCreate(cuvsIvfFlatSearchParams_t* params) { - try { - *params = new ivfFlatSearchParams{.n_probes = 20}; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions( + [=] { *params = new ivfFlatSearchParams{.n_probes = 20}; }); } extern "C" cuvsError_t cuvsIvfFlatSearchParamsDestroy(cuvsIvfFlatSearchParams_t params) { - try { - delete params; - return CUVS_SUCCESS; - } catch (...) { - return CUVS_ERROR; - } + return cuvs::core::translate_exceptions([=] { delete params; }); } diff --git a/cpp/test/neighbors/run_brute_force_c.c b/cpp/test/neighbors/run_brute_force_c.c index 9c7af13a60..ed775a2d6c 100644 --- a/cpp/test/neighbors/run_brute_force_c.c +++ b/cpp/test/neighbors/run_brute_force_c.c @@ -44,10 +44,10 @@ void run_brute_force(int64_t n_rows, // create index cuvsBruteForceIndex_t index; - bruteForceIndexCreate(&index); + cuvsBruteForceIndexCreate(&index); // build index - bruteForceBuild(res, &dataset_tensor, metric, 0.0f, index); + cuvsBruteForceBuild(res, &dataset_tensor, metric, 0.0f, index); // create queries DLTensor DLManagedTensor queries_tensor; @@ -86,9 +86,9 @@ void run_brute_force(int64_t n_rows, distances_tensor.dl_tensor.strides = NULL; // search index - bruteForceSearch(res, index, &queries_tensor, &neighbors_tensor, &distances_tensor); + cuvsBruteForceSearch(res, index, &queries_tensor, &neighbors_tensor, &distances_tensor); // de-allocate index and res - bruteForceIndexDestroy(index); + cuvsBruteForceIndexDestroy(index); cuvsResourcesDestroy(res); } diff --git a/python/cuvs/cuvs/distance_type.pxd b/python/cuvs/cuvs/distance_type.pxd new file mode 100644 index 0000000000..a1f0366a56 --- /dev/null +++ b/python/cuvs/cuvs/distance_type.pxd @@ -0,0 +1,41 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + + +cdef extern from "cuvs/distance/distance_types.h" nogil: + ctypedef enum DistanceType: + L2Expanded + L2SqrtExpanded + CosineExpanded + L1 + L2Unexpanded + L2SqrtUnexpanded + InnerProduct + Linf + Canberra + LpUnexpanded + CorrelationExpanded + JaccardExpanded + HellingerExpanded + Haversine + BrayCurtis + JensenShannon + HammingUnexpanded + KLDivergence + RusselRaoExpanded + DiceExpanded + Precomputed diff --git a/python/cuvs/cuvs/neighbors/CMakeLists.txt b/python/cuvs/cuvs/neighbors/CMakeLists.txt index eaf418c60f..481ec9915f 100644 --- a/python/cuvs/cuvs/neighbors/CMakeLists.txt +++ b/python/cuvs/cuvs/neighbors/CMakeLists.txt @@ -12,4 +12,5 @@ # the License. # ============================================================================= +add_subdirectory(brute_force) add_subdirectory(cagra) diff --git a/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt b/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt new file mode 100644 index 0000000000..8534370aee --- /dev/null +++ b/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt @@ -0,0 +1,25 @@ +# ============================================================================= +# Copyright (c) 2023-2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +# Set the list of Cython files to build +set(cython_sources brute_force.pyx) +set(linked_libraries cuvs::cuvs cuvs_c) + +# Build all of the Cython targets +rapids_cython_create_modules( + CXX + SOURCE_FILES "${cython_sources}" + LINKED_LIBRARIES "${linked_libraries}" ASSOCIATED_TARGETS cuvs MODULE_PREFIX + neighbors_brute_force_ +) diff --git a/python/cuvs/cuvs/neighbors/brute_force/__init__.pxd b/python/cuvs/cuvs/neighbors/brute_force/__init__.pxd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/neighbors/brute_force/__init__.py b/python/cuvs/cuvs/neighbors/brute_force/__init__.py new file mode 100644 index 0000000000..b88c4b4648 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/brute_force/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from .brute_force import Index, build, search + +__all__ = ["Index", "build", "search"] diff --git a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd new file mode 100644 index 0000000000..77e484fba2 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd @@ -0,0 +1,47 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +from libc.stdint cimport uintptr_t + +from cuvs.common.c_api cimport cuvsError_t, cuvsResources_t +from cuvs.common.cydlpack cimport DLDataType, DLManagedTensor +from cuvs.distance_type cimport DistanceType + + +cdef extern from "cuvs/neighbors/brute_force.h" nogil: + + ctypedef struct cuvsBruteForceIndex: + uintptr_t addr + DLDataType dtype + + ctypedef cuvsBruteForceIndex* cuvsBruteForceIndex_t + + cuvsError_t cuvsBruteForceIndexCreate(cuvsBruteForceIndex_t* index) + + cuvsError_t cuvsBruteForceIndexDestroy(cuvsBruteForceIndex_t index) + + cuvsError_t cuvsBruteForceBuild(cuvsResources_t res, + DLManagedTensor* dataset, + DistanceType metric, + float metric_arg, + cuvsBruteForceIndex_t index) except + + + cuvsError_t cuvsBruteForceSearch(cuvsResources_t res, + cuvsBruteForceIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances) except + diff --git a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx new file mode 100644 index 0000000000..ccb10e3055 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx @@ -0,0 +1,216 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +import numpy as np + +cimport cuvs.common.cydlpack + +from cuvs.common.resources import auto_sync_resources + +from cython.operator cimport dereference as deref +from libc.stdint cimport uint32_t +from libcpp cimport bool + +from cuvs.common cimport cydlpack +from cuvs.distance_type cimport DistanceType + +from pylibraft.common import auto_convert_output, cai_wrapper, device_ndarray +from pylibraft.common.cai_wrapper import wrap_array +from pylibraft.common.interruptible import cuda_interruptible +from pylibraft.distance.pairwise_distance import DISTANCE_TYPES +from pylibraft.neighbors.common import _check_input_array + +from cuvs.common.c_api cimport cuvsResources_t + +from cuvs.common.exceptions import check_cuvs + + +cdef class Index: + """ + Brute Force index object. This object stores the trained Brute Force + which can be used to perform nearest neighbors searches. + """ + + cdef cuvsBruteForceIndex_t index + cdef bool trained + + def __cinit__(self): + self.trained = False + check_cuvs(cuvsBruteForceIndexCreate(&self.index)) + + def __dealloc__(self): + if self.index is not NULL: + check_cuvs(cuvsBruteForceIndexDestroy(self.index)) + + @property + def trained(self): + return self.trained + + def __repr__(self): + return "Index(type=BruteForce)" + + +@auto_sync_resources +def build(dataset, metric="sqeuclidean", metric_arg=2.0, resources=None): + """ + Build the Brute Force index from the dataset for efficient search. + The following distance metrics are supported: + - L2 + + Parameters + ---------- + dataset : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + metric : Distance metric to use. Default is sqeuclidean + metric_arg : value of 'p' for Minkowski distances + {resources_docstring} + + Returns + ------- + index: cuvs.neighbors.brute_force.Index + + Examples + -------- + + >>> import cupy as cp + >>> from cuvs.neighbors import brute_force + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> k = 10 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> index = brute_force.build(dataset, metric="cosine") + >>> distances, neighbors = brute_force.search(index, dataset, k) + >>> distances = cp.asarray(distances) + >>> neighbors = cp.asarray(neighbors) + """ + + dataset_ai = wrap_array(dataset) + _check_input_array(dataset_ai, [np.dtype('float32')]) + + cdef cuvsResources_t res = resources.get_c_obj() + + cdef DistanceType c_metric = DISTANCE_TYPES[metric] + cdef Index idx = Index() + cdef cydlpack.DLManagedTensor* dataset_dlpack = \ + cydlpack.dlpack_c(dataset_ai) + + with cuda_interruptible(): + check_cuvs(cuvsBruteForceBuild( + res, + dataset_dlpack, + c_metric, + metric_arg, + idx.index + )) + idx.trained = True + + return idx + + +@auto_sync_resources +@auto_convert_output +def search(Index index, + queries, + k, + neighbors=None, + distances=None, + resources=None): + """ + Find the k nearest neighbors for each query. + + Parameters + ---------- + index : Index + Trained Brute Force index. + queries : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + k : int + The number of neighbors. + neighbors : Optional CUDA array interface compliant matrix shape + (n_queries, k), dtype int64_t. If supplied, neighbor + indices will be written here in-place. (default None) + distances : Optional CUDA array interface compliant matrix shape + (n_queries, k) If supplied, the distances to the + neighbors will be written here in-place. (default None) + {resources_docstring} + + Examples + -------- + >>> import cupy as cp + >>> from cuvs.neighbors import brute_force + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> # Build index + >>> index = brute_force.build(dataset, metric="sqeuclidean") + >>> # Search using the built index + >>> queries = cp.random.random_sample((n_queries, n_features), + ... dtype=cp.float32) + >>> k = 10 + >>> # Using a pooling allocator reduces overhead of temporary array + >>> # creation during search. This is useful if multiple searches + >>> # are performad with same query size. + >>> distances, neighbors = brute_force.search(index, queries, k) + >>> neighbors = cp.asarray(neighbors) + >>> distances = cp.asarray(distances) + """ + if not index.trained: + raise ValueError("Index needs to be built before calling search.") + + cdef cuvsResources_t res = resources.get_c_obj() + + queries_cai = wrap_array(queries) + _check_input_array(queries_cai, [np.dtype('float32')]) + + cdef uint32_t n_queries = queries_cai.shape[0] + + if neighbors is None: + neighbors = device_ndarray.empty((n_queries, k), dtype='int64') + + neighbors_cai = wrap_array(neighbors) + _check_input_array(neighbors_cai, [np.dtype('int64')], + exp_rows=n_queries, exp_cols=k) + + if distances is None: + distances = device_ndarray.empty((n_queries, k), dtype='float32') + + distances_cai = wrap_array(distances) + _check_input_array(distances_cai, [np.dtype('float32')], + exp_rows=n_queries, exp_cols=k) + + cdef cuvsError_t search_status + cdef cydlpack.DLManagedTensor* queries_dlpack = \ + cydlpack.dlpack_c(queries_cai) + cdef cydlpack.DLManagedTensor* neighbors_dlpack = \ + cydlpack.dlpack_c(neighbors_cai) + cdef cydlpack.DLManagedTensor* distances_dlpack = \ + cydlpack.dlpack_c(distances_cai) + + with cuda_interruptible(): + check_cuvs(cuvsBruteForceSearch( + res, + index.index, + queries_dlpack, + neighbors_dlpack, + distances_dlpack + )) + + return (distances, neighbors) diff --git a/python/cuvs/cuvs/test/test_brute_force.py b/python/cuvs/cuvs/test/test_brute_force.py new file mode 100644 index 0000000000..146ab59f01 --- /dev/null +++ b/python/cuvs/cuvs/test/test_brute_force.py @@ -0,0 +1,90 @@ +# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from pylibraft.common import device_ndarray +from scipy.spatial.distance import cdist + +from cuvs.neighbors import brute_force + + +@pytest.mark.parametrize("n_index_rows", [32, 100]) +@pytest.mark.parametrize("n_query_rows", [32, 100]) +@pytest.mark.parametrize("n_cols", [40, 100]) +@pytest.mark.parametrize("k", [1, 5, 32]) +@pytest.mark.parametrize( + "metric", + [ + "euclidean", + "cityblock", + "chebyshev", + "canberra", + "correlation", + "russellrao", + "cosine", + "sqeuclidean", + # "inner_product", + ], +) +@pytest.mark.parametrize("inplace", [True, False]) +@pytest.mark.parametrize("dtype", [np.float32]) +def test_brute_force_knn( + n_index_rows, n_query_rows, n_cols, k, inplace, metric, dtype +): + index = np.random.random_sample((n_index_rows, n_cols)).astype(dtype) + queries = np.random.random_sample((n_query_rows, n_cols)).astype(dtype) + + # RussellRao expects boolean arrays + if metric == "russellrao": + index[index < 0.5] = 0.0 + index[index >= 0.5] = 1.0 + queries[queries < 0.5] = 0.0 + queries[queries >= 0.5] = 1.0 + + indices = np.zeros((n_query_rows, k), dtype="int64") + distances = np.zeros((n_query_rows, k), dtype=dtype) + + index_device = device_ndarray(index) + queries_device = device_ndarray(queries) + indices_device = device_ndarray(indices) + distances_device = device_ndarray(distances) + + brute_force_index = brute_force.build(index_device, metric) + ret_distances, ret_indices = brute_force.search( + brute_force_index, + queries_device, + k, + neighbors=indices_device, + distances=distances_device, + ) + + pw_dists = cdist(queries, index, metric=metric) + + distances_device = ret_distances if not inplace else distances_device + + actual_distances = distances_device.copy_to_host() + + actual_distances[actual_distances <= 1e-5] = 0.0 + argsort = np.argsort(pw_dists, axis=1) + + for i in range(pw_dists.shape[0]): + expected_indices = argsort[i] + gpu_dists = actual_distances[i] + + cpu_ordered = pw_dists[i, expected_indices] + np.testing.assert_allclose( + cpu_ordered[:k], gpu_dists, atol=1e-3, rtol=1e-3 + ) diff --git a/rust/cuvs-sys/build.rs b/rust/cuvs-sys/build.rs index ec96725695..f521f8af3f 100644 --- a/rust/cuvs-sys/build.rs +++ b/rust/cuvs-sys/build.rs @@ -98,9 +98,9 @@ fn main() { // generated) .must_use_type("cuvsError_t") // Only generate bindings for cuvs/cagra types and functions - .allowlist_type("(cuvs|cagra|DL).*") - .allowlist_function("(cuvs|cagra).*") - .rustified_enum("(cuvs|cagra|DL).*") + .allowlist_type("(cuvs|bruteForce|cagra|DL).*") + .allowlist_function("(cuvs|bruteForce|cagra).*") + .rustified_enum("(cuvs|cagra|DL|DistanceType).*") // also need some basic cuda mem functions for copying data .allowlist_function("(cudaMemcpyAsync|cudaMemcpy)") .rustified_enum("cudaError") diff --git a/rust/cuvs-sys/cuvs_c_wrapper.h b/rust/cuvs-sys/cuvs_c_wrapper.h index ccca82632d..e2c35f12dc 100644 --- a/rust/cuvs-sys/cuvs_c_wrapper.h +++ b/rust/cuvs-sys/cuvs_c_wrapper.h @@ -17,4 +17,5 @@ // wrapper file containing all the C-API's we should automatically be creating rust // bindings for #include +#include #include diff --git a/rust/cuvs/src/brute_force.rs b/rust/cuvs/src/brute_force.rs new file mode 100644 index 0000000000..e2e03c2367 --- /dev/null +++ b/rust/cuvs/src/brute_force.rs @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::io::{stderr, Write}; + +use crate::distance_type::DistanceType; +use crate::dlpack::ManagedTensor; +use crate::error::{check_cuvs, Result}; +use crate::resources::Resources; + +/// Brute Force KNN Index +#[derive(Debug)] +pub struct Index(ffi::cuvsBruteForceIndex_t); + +impl Index { + /// Builds a new Brute Force KNN Index from the dataset for efficient search. + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `metric` - DistanceType to use for building the index + /// * `metric_arg` - Optional value of `p` for Minkowski distances + /// * `dataset` - A row-major matrix on either the host or device to index + pub fn build>( + res: &Resources, + metric: DistanceType, + metric_arg: Option, + dataset: T, + ) -> Result { + let dataset: ManagedTensor = dataset.into(); + let index = Index::new()?; + unsafe { + check_cuvs(ffi::cuvsBruteForceBuild( + res.0, + dataset.as_ptr(), + metric, + metric_arg.unwrap_or(2.0), + index.0, + ))?; + } + Ok(index) + } + + /// Creates a new empty index + pub fn new() -> Result { + unsafe { + let mut index = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsBruteForceIndexCreate(index.as_mut_ptr()))?; + Ok(Index(index.assume_init())) + } + } + + /// Perform a Nearest Neighbors search on the Index + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `queries` - A matrix in device memory to query for + /// * `neighbors` - Matrix in device memory that receives the indices of the nearest neighbors + /// * `distances` - Matrix in device memory that receives the distances of the nearest neighbors + pub fn search( + self, + res: &Resources, + queries: &ManagedTensor, + neighbors: &ManagedTensor, + distances: &ManagedTensor, + ) -> Result<()> { + unsafe { + check_cuvs(ffi::cuvsBruteForceSearch( + res.0, + self.0, + queries.as_ptr(), + neighbors.as_ptr(), + distances.as_ptr(), + )) + } + } +} + +impl Drop for Index { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsBruteForceIndexDestroy(self.0) }) { + write!(stderr(), "failed to call cagraIndexDestroy {:?}", e) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ndarray::s; + use ndarray_rand::rand_distr::Uniform; + use ndarray_rand::RandomExt; + + fn test_bfknn(metric: DistanceType) { + let res = Resources::new().unwrap(); + + // Create a new random dataset to index + let n_datapoints = 16; + let n_features = 8; + let dataset_host = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + let dataset = ManagedTensor::from(&dataset_host) + .to_device(&res) + .unwrap(); + + println!("dataset {:#?}", dataset_host); + + // build the brute force index + let index = + Index::build(&res, metric, None, dataset).expect("failed to create brute force index"); + + res.sync_stream().unwrap(); + + // use the first 4 points from the dataset as queries : will test that we get them back + // as their own nearest neighbor + let n_queries = 4; + let queries = dataset_host.slice(s![0..n_queries, ..]); + + let k = 4; + + println!("queries! {:#?}", queries); + let queries = ManagedTensor::from(&queries).to_device(&res).unwrap(); + let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); + let neighbors = ManagedTensor::from(&neighbors_host) + .to_device(&res) + .unwrap(); + + let mut distances_host = ndarray::Array::::zeros((n_queries, k)); + let distances = ManagedTensor::from(&distances_host) + .to_device(&res) + .unwrap(); + + index + .search(&res, &queries, &neighbors, &distances) + .unwrap(); + + // Copy back to host memory + distances.to_host(&res, &mut distances_host).unwrap(); + neighbors.to_host(&res, &mut neighbors_host).unwrap(); + res.sync_stream().unwrap(); + + println!("distances {:#?}", distances_host); + println!("neighbors {:#?}", neighbors_host); + + // nearest neighbors should be themselves, since queries are from the + // dataset + assert_eq!(neighbors_host[[0, 0]], 0); + assert_eq!(neighbors_host[[1, 0]], 1); + assert_eq!(neighbors_host[[2, 0]], 2); + assert_eq!(neighbors_host[[3, 0]], 3); + } + +/* + #[test] + fn test_cosine() { + test_bfknn(DistanceType::CosineExpanded); + } +*/ + + #[test] + fn test_l2() { + test_bfknn(DistanceType::L2Expanded); + } +} diff --git a/rust/cuvs/src/distance_type.rs b/rust/cuvs/src/distance_type.rs new file mode 100644 index 0000000000..4ac3e9164b --- /dev/null +++ b/rust/cuvs/src/distance_type.rs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pub type DistanceType = ffi::DistanceType; diff --git a/rust/cuvs/src/dlpack.rs b/rust/cuvs/src/dlpack.rs index 238caec9d7..b74484a5c9 100644 --- a/rust/cuvs/src/dlpack.rs +++ b/rust/cuvs/src/dlpack.rs @@ -67,16 +67,17 @@ impl ManagedTensor { D: ndarray::Dimension, >( &self, - _res: &Resources, + res: &Resources, arr: &mut ndarray::ArrayBase, ) -> Result<()> { unsafe { let bytes = dl_tensor_bytes(&self.0.dl_tensor); - check_cuda(ffi::cudaMemcpy( + check_cuda(ffi::cudaMemcpyAsync( arr.as_mut_ptr() as *mut std::ffi::c_void, self.0.dl_tensor.data, bytes, ffi::cudaMemcpyKind_cudaMemcpyDefault, + res.get_cuda_stream()?, ))?; Ok(()) } @@ -169,6 +170,16 @@ impl IntoDtype for i32 { } } +impl IntoDtype for i64 { + fn ffi_dtype() -> ffi::DLDataType { + ffi::DLDataType { + code: ffi::DLDataTypeCode::kDLInt as _, + bits: 64, + lanes: 1, + } + } +} + impl IntoDtype for u32 { fn ffi_dtype() -> ffi::DLDataType { ffi::DLDataType { diff --git a/rust/cuvs/src/lib.rs b/rust/cuvs/src/lib.rs index 273f047237..de974dad7a 100644 --- a/rust/cuvs/src/lib.rs +++ b/rust/cuvs/src/lib.rs @@ -18,8 +18,9 @@ //! //! This crate provides Rust bindings for cuVS, allowing you to run //! approximate nearest neighbors search on the GPU. - +pub mod brute_force; pub mod cagra; +pub mod distance_type; mod dlpack; mod error; mod resources; From 36926d2f21df8650d4ebb8f7b86b6b0c9ddfe7d8 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 9 Apr 2024 11:39:39 -0700 Subject: [PATCH 028/623] Update devcontainers to use cuda12.2 (#72) Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/72 --- .devcontainer/cuda11.8-pip/devcontainer.json | 12 ++++++++++- .../devcontainer.json | 4 ++-- .../devcontainer.json | 20 ++++++++++++++----- .github/workflows/pr.yaml | 2 ++ python/cuvs/CMakeLists.txt | 18 +++++++++-------- python/cuvs/cuvs/common/CMakeLists.txt | 2 +- .../cuvs/cuvs/neighbors/cagra/CMakeLists.txt | 2 +- 7 files changed, 42 insertions(+), 18 deletions(-) rename .devcontainer/{cuda12.0-conda => cuda12.2-conda}/devcontainer.json (93%) rename .devcontainer/{cuda12.0-pip => cuda12.2-pip}/devcontainer.json (73%) diff --git a/.devcontainer/cuda11.8-pip/devcontainer.json b/.devcontainer/cuda11.8-pip/devcontainer.json index 39b7971e79..c4abd5b8c3 100644 --- a/.devcontainer/cuda11.8-pip/devcontainer.json +++ b/.devcontainer/cuda11.8-pip/devcontainer.json @@ -10,11 +10,21 @@ }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": {"version": "1.14.1"}, + "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": { + "version": "1.14.1" + }, + "ghcr.io/rapidsai/devcontainers/features/cuda:24.6": { + "version": "11.8", + "installcuBLAS": true, + "installcuSOLVER": true, + "installcuRAND": true, + "installcuSPARSE": true + }, "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/ucx", + "ghcr.io/rapidsai/devcontainers/features/cuda", "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" ], "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda11.8-venvs}"], diff --git a/.devcontainer/cuda12.0-conda/devcontainer.json b/.devcontainer/cuda12.2-conda/devcontainer.json similarity index 93% rename from .devcontainer/cuda12.0-conda/devcontainer.json rename to .devcontainer/cuda12.2-conda/devcontainer.json index 4d9cbf33d8..6c15504977 100644 --- a/.devcontainer/cuda12.0-conda/devcontainer.json +++ b/.devcontainer/cuda12.2-conda/devcontainer.json @@ -15,7 +15,7 @@ "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" ], - "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config,conda/pkgs,conda/${localWorkspaceFolderBasename}-cuda12.0-envs}"], + "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config,conda/pkgs,conda/${localWorkspaceFolderBasename}-cuda12.2-envs}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; . rapids-post-attach-command; fi"], "workspaceFolder": "/home/coder", "workspaceMount": "source=${localWorkspaceFolder},target=/home/coder/cuvs,type=bind,consistency=consistent", @@ -24,7 +24,7 @@ "source=${localWorkspaceFolder}/../.cache,target=/home/coder/.cache,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.config,target=/home/coder/.config,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.conda/pkgs,target=/home/coder/.conda/pkgs,type=bind,consistency=consistent", - "source=${localWorkspaceFolder}/../.conda/${localWorkspaceFolderBasename}-cuda12.0-envs,target=/home/coder/.conda/envs,type=bind,consistency=consistent" + "source=${localWorkspaceFolder}/../.conda/${localWorkspaceFolderBasename}-cuda12.2-envs,target=/home/coder/.conda/envs,type=bind,consistency=consistent" ], "customizations": { "vscode": { diff --git a/.devcontainer/cuda12.0-pip/devcontainer.json b/.devcontainer/cuda12.2-pip/devcontainer.json similarity index 73% rename from .devcontainer/cuda12.0-pip/devcontainer.json rename to .devcontainer/cuda12.2-pip/devcontainer.json index 2a77539cff..8dd06ae6c4 100644 --- a/.devcontainer/cuda12.0-pip/devcontainer.json +++ b/.devcontainer/cuda12.2-pip/devcontainer.json @@ -3,21 +3,31 @@ "context": "${localWorkspaceFolder}/.devcontainer", "dockerfile": "${localWorkspaceFolder}/.devcontainer/Dockerfile", "args": { - "CUDA": "12.0", + "CUDA": "12.2", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.06-cpp-llvm16-cuda12.0-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.06-cpp-cuda12.2-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": {"version": "1.14.1"}, + "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": { + "version": "1.14.1" + }, + "ghcr.io/rapidsai/devcontainers/features/cuda:24.6": { + "version": "12.2", + "installcuBLAS": true, + "installcuSOLVER": true, + "installcuRAND": true, + "installcuSPARSE": true + }, "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/ucx", + "ghcr.io/rapidsai/devcontainers/features/cuda", "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" ], - "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda12.0-venvs}"], + "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda12.2-venvs}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; . rapids-post-attach-command; fi"], "workspaceFolder": "/home/coder", "workspaceMount": "source=${localWorkspaceFolder},target=/home/coder/cuvs,type=bind,consistency=consistent", @@ -25,7 +35,7 @@ "source=${localWorkspaceFolder}/../.aws,target=/home/coder/.aws,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.cache,target=/home/coder/.cache,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.config,target=/home/coder/.config,type=bind,consistency=consistent", - "source=${localWorkspaceFolder}/../.local/share/${localWorkspaceFolderBasename}-cuda12.0-venvs,target=/home/coder/.local/share/venvs,type=bind,consistency=consistent" + "source=${localWorkspaceFolder}/../.local/share/${localWorkspaceFolderBasename}-cuda12.2-venvs,target=/home/coder/.local/share/venvs,type=bind,consistency=consistent" ], "customizations": { "vscode": { diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 652092dde5..3e244cc109 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -101,6 +101,8 @@ jobs: secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.06 with: + arch: '["amd64"]' + cuda: '["12.2"]' build_command: | sccache -z; build-all -DBUILD_PRIMS_BENCH=ON -DBUILD_ANN_BENCH=ON --verbose; diff --git a/python/cuvs/CMakeLists.txt b/python/cuvs/CMakeLists.txt index 36a62044ae..db6f256751 100644 --- a/python/cuvs/CMakeLists.txt +++ b/python/cuvs/CMakeLists.txt @@ -31,17 +31,19 @@ project( C CXX CUDA ) -################################################################################ -# - User Options -------------------------------------------------------------- +# ################################################################################################## +# * User Options -------------------------------------------------------------- option(FIND_CUVS_CPP "Search for existing CUVS C++ installations before defaulting to local files" OFF ) -message("CUVS_PY: Searching for existing cuVS C/C++ installations before defaulting to local files: ${FIND_CUVS_CPP}") +message( + "CUVS_PY: Searching for existing cuVS C/C++ installations before defaulting to local files: ${FIND_CUVS_CPP}" +) -################################################################################ -# - Process User Options ------------------------------------------------------ +# ################################################################################################## +# * Process User Options ------------------------------------------------------ include(rapids-cmake) include(rapids-cpm) @@ -53,7 +55,7 @@ rapids_cpm_init() # If the user requested it we attempt to find CUVS. if(FIND_CUVS_CPP) - find_package(cuvs "${RAPIDS_VERSION}" REQUIRED) + find_package(cuvs "${RAPIDS_VERSION}" REQUIRED COMPONENTS c_api) include(../../cpp/cmake/thirdparty/get_dlpack.cmake) else() set(cuvs_FOUND OFF) @@ -73,8 +75,8 @@ if(NOT cuvs_FOUND) install(TARGETS cuvs cuvs_c DESTINATION ${cython_lib_dir}) endif() -################################################################################ -# - Build Cython artifacts ----------------------------------------------------- +# ################################################################################################## +# * Build Cython artifacts ----------------------------------------------------- rapids_cython_init() diff --git a/python/cuvs/cuvs/common/CMakeLists.txt b/python/cuvs/cuvs/common/CMakeLists.txt index b477fdb322..3de3805af8 100644 --- a/python/cuvs/cuvs/common/CMakeLists.txt +++ b/python/cuvs/cuvs/common/CMakeLists.txt @@ -14,7 +14,7 @@ # Set the list of Cython files to build set(cython_sources cydlpack.pyx exceptions.pyx) -set(linked_libraries cuvs::cuvs cuvs_c) +set(linked_libraries cuvs::cuvs cuvs::c_api) # Build all of the Cython targets rapids_cython_create_modules( diff --git a/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt b/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt index 377cfe7793..87e6597fe1 100644 --- a/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt +++ b/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt @@ -14,7 +14,7 @@ # Set the list of Cython files to build set(cython_sources cagra.pyx) -set(linked_libraries cuvs::cuvs cuvs_c) +set(linked_libraries cuvs::cuvs cuvs::c_api) # Build all of the Cython targets rapids_cython_create_modules( From 1e3206afe9674d52da013e7f93b45144067f4f37 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 9 Apr 2024 12:30:15 -0700 Subject: [PATCH 029/623] Enable forward-merger ops-bot plugin (#70) Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Jake Awe (https://github.com/AyodeAwe) - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/70 --- .github/ops-bot.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ops-bot.yaml b/.github/ops-bot.yaml index 9a0b415503..2ed5231aef 100644 --- a/.github/ops-bot.yaml +++ b/.github/ops-bot.yaml @@ -6,3 +6,4 @@ branch_checker: true label_checker: true release_drafter: true recently_updated: true +forward_merger: true From e7e3f5684509eca24fca187b30d56ba62f66d0aa Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Tue, 9 Apr 2024 15:48:29 -0400 Subject: [PATCH 030/623] Fixing brute-force python build --- python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt b/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt index 8534370aee..4806fb9fc0 100644 --- a/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt +++ b/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt @@ -14,7 +14,7 @@ # Set the list of Cython files to build set(cython_sources brute_force.pyx) -set(linked_libraries cuvs::cuvs cuvs_c) +set(linked_libraries cuvs::cuvs cuvs::c_api) # Build all of the Cython targets rapids_cython_create_modules( From 4488626373fda37656074e805d948875646c6647 Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Tue, 9 Apr 2024 18:31:32 -0400 Subject: [PATCH 031/623] Add `lucene-cuvs` to integrations section of docs (#73) Authors: - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Ben Frederickson (https://github.com/benfred) URL: https://github.com/rapidsai/cuvs/pull/73 --- docs/source/integrations.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/source/integrations.rst b/docs/source/integrations.rst index 6c6e2ce8ca..46cbaa52c9 100644 --- a/docs/source/integrations.rst +++ b/docs/source/integrations.rst @@ -5,8 +5,10 @@ Aside from using cuVS directly, it can be consumed through a number of sdk and v - `FAISS`_ - `Milvus`_ +- `Lucene` _ - `Kinetica`_ + FAISS ----- @@ -32,9 +34,17 @@ The GPU indexes can be enabled by using the index types prefixed with `GPU_`, as Milvus will be migrating their GPU support from RAFT to cuVS as we continue to move the vector search algorithms out of RAFT and into cuVS. +Lucene +------ + +An experimental Lucene connector for cuVS enables GPU-accelerated vector search indexes through Lucene. Initial benchmarks are showing that this connector can drastically improve the performance of both indexing and search in Lucene. This connector will continue to be improved over time and any interested developers are encouraged to contribute. + +Install and evaluate the `lucene-cuvs` connector on `Github `_. + + Kinetica -------- Starting with release 7.2, Kinetica supports the graph-based the CAGRA algorithm from RAFT. Kinetica will continue to improve its support over coming versions, while also migrating to cuVS as we work to move the vector search algorithms out of RAFT and into cuVS. -Kinetica currently offers the ability to create a CAGRA index in a SQL `CREATE_TABLE` statement, as outlined in their `vector search indexing docs `_. Kinetica is not open source, but the RAFT indexes can be enabled in the developer edition, which can be installed `here `_. \ No newline at end of file +Kinetica currently offers the ability to create a CAGRA index in a SQL `CREATE_TABLE` statement, as outlined in their `vector search indexing docs `_. Kinetica is not open source, but the RAFT indexes can be enabled in the developer edition, which can be installed `here `_. From 6ad58e3f928b09794fc63f12c22f766be6027822 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 9 Apr 2024 15:34:51 -0700 Subject: [PATCH 032/623] Add Cagra-Q compression to the python and rust api's (#68) Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/68 --- python/cuvs/cuvs/neighbors/cagra/__init__.py | 18 ++- python/cuvs/cuvs/neighbors/cagra/cagra.pxd | 17 +++ python/cuvs/cuvs/neighbors/cagra/cagra.pyx | 97 +++++++++++++++- python/cuvs/cuvs/test/test_cagra.py | 10 ++ rust/cuvs/src/cagra/index.rs | 18 ++- rust/cuvs/src/cagra/index_params.rs | 115 ++++++++++++++++++- rust/cuvs/src/cagra/mod.rs | 2 +- 7 files changed, 266 insertions(+), 11 deletions(-) diff --git a/python/cuvs/cuvs/neighbors/cagra/__init__.py b/python/cuvs/cuvs/neighbors/cagra/__init__.py index 657c7d366e..0b487d7ab6 100644 --- a/python/cuvs/cuvs/neighbors/cagra/__init__.py +++ b/python/cuvs/cuvs/neighbors/cagra/__init__.py @@ -13,6 +13,20 @@ # limitations under the License. -from .cagra import Index, IndexParams, SearchParams, build_index, search +from .cagra import ( + CompressionParams, + Index, + IndexParams, + SearchParams, + build_index, + search, +) -__all__ = ["Index", "IndexParams", "SearchParams", "build_index", "search"] +__all__ = [ + "CompressionParams", + "Index", + "IndexParams", + "SearchParams", + "build_index", + "search", +] diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pxd b/python/cuvs/cuvs/neighbors/cagra/cagra.pxd index 4293bdc073..d229a3b09d 100644 --- a/python/cuvs/cuvs/neighbors/cagra/cagra.pxd +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pxd @@ -34,11 +34,22 @@ cdef extern from "cuvs/neighbors/cagra.h" nogil: IVF_PQ NN_DESCENT + ctypedef struct cuvsCagraCompressionParams: + uint32_t pq_bits + uint32_t pq_dim + uint32_t vq_n_centers + uint32_t kmeans_n_iters + double vq_kmeans_trainset_fraction + double pq_kmeans_trainset_fraction + + ctypedef cuvsCagraCompressionParams* cuvsCagraCompressionParams_t + ctypedef struct cuvsCagraIndexParams: size_t intermediate_graph_degree size_t graph_degree cuvsCagraGraphBuildAlgo build_algo size_t nn_descent_niter + cuvsCagraCompressionParams_t compression ctypedef cuvsCagraIndexParams* cuvsCagraIndexParams_t @@ -74,6 +85,12 @@ cdef extern from "cuvs/neighbors/cagra.h" nogil: ctypedef cuvsCagraIndex* cuvsCagraIndex_t + cuvsError_t cuvsCagraCompressionParamsCreate( + cuvsCagraCompressionParams_t* params) + + cuvsError_t cuvsCagraCompressionParamsDestroy( + cuvsCagraCompressionParams_t index) + cuvsError_t cuvsCagraIndexParamsCreate(cuvsCagraIndexParams_t* params) cuvsError_t cuvsCagraIndexParamsDestroy(cuvsCagraIndexParams_t index) diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx index 818c93940a..f5f94aba5e 100644 --- a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx @@ -43,6 +43,82 @@ from libc.stdint cimport ( from cuvs.common.exceptions import check_cuvs +cdef class CompressionParams: + """ + Parameters for VPQ Compression + + Parameters + ---------- + pq_bits: int + The bit length of the vector element after compression by PQ. + Possible values: [4, 5, 6, 7, 8]. The smaller the 'pq_bits', the + smaller the index size and the better the search performance, but + the lower the recall. + pq_dim: int + The dimensionality of the vector after compression by PQ. When zero, + an optimal value is selected using a heuristic. + vq_n_centers: int + Vector Quantization (VQ) codebook size - number of "coarse cluster + centers". When zero, an optimal value is selected using a heuristic. + kmeans_n_iters: int + The number of iterations searching for kmeans centers (both VQ & PQ + phases). + vq_kmeans_trainset_fraction: float + The fraction of data to use during iterative kmeans building (VQ + phase). When zero, an optimal value is selected using a heuristic. + vq_kmeans_trainset_fraction: float + The fraction of data to use during iterative kmeans building (PQ + phase). When zero, an optimal value is selected using a heuristic. + """ + cdef cuvsCagraCompressionParams * params + + def __cinit__(self): + check_cuvs(cuvsCagraCompressionParamsCreate(&self.params)) + + def __dealloc__(self): + check_cuvs(cuvsCagraCompressionParamsDestroy(self.params)) + + def __init__(self, *, + pq_bits=8, + pq_dim=0, + vq_n_centers=0, + kmeans_n_iters=25, + vq_kmeans_trainset_fraction=0.0, + pq_kmeans_trainset_fraction=0.0): + self.params.pq_bits = pq_bits + self.params.pq_dim = pq_dim + self.params.vq_n_centers = vq_n_centers + self.params.kmeans_n_iters = kmeans_n_iters + self.params.vq_kmeans_trainset_fraction = vq_kmeans_trainset_fraction + self.params.pq_kmeans_trainset_fraction = pq_kmeans_trainset_fraction + + @property + def pq_bits(self): + return self.params.pq_bits + + @property + def pq_dim(self): + return self.params.pq_dim + + @property + def vq_n_centers(self): + return self.params.vq_n_centers + + @property + def kmeans_n_iters(self): + return self.params.kmeans_n_iters + + @property + def vq_kmeans_trainset_fraction(self): + return self.params.vq_kmeans_trainset_fraction + + @property + def pq_kmeans_trainset_fraction(self): + return self.params.pq_kmeans_trainset_fraction + + def get_handle(self): + return self.params + cdef class IndexParams: """ Parameters to build index for CAGRA nearest neighbor search @@ -64,17 +140,30 @@ cdef class IndexParams: - nn_descent (experimental) will use the NN-Descent algorithm for building the knn graph. It is expected to be generally faster than ivf_pq. + compression: CompressionParams, optional + If compression is desired should be a CompressionParams object. If None + compression will be disabled. """ cdef cuvsCagraIndexParams* params + # hold on to a reference to the compression, to keep from being GC'ed + cdef public object compression + + def __cinit__(self): + check_cuvs(cuvsCagraIndexParamsCreate(&self.params)) + self.compression = None + + def __dealloc__(self): + check_cuvs(cuvsCagraIndexParamsDestroy(self.params)) + def __init__(self, *, metric="sqeuclidean", intermediate_graph_degree=128, graph_degree=64, build_algo="ivf_pq", - nn_descent_niter=20): - cuvsCagraIndexParamsCreate(&self.params) + nn_descent_niter=20, + compression=None): # todo (dgd): enable once other metrics are present # and exposed in cuVS C API @@ -87,6 +176,10 @@ cdef class IndexParams: elif build_algo == "nn_descent": self.params.build_algo = cuvsCagraGraphBuildAlgo.NN_DESCENT self.params.nn_descent_niter = nn_descent_niter + if compression is not None: + self.compression = compression + self.params.compression = \ + compression.get_handle() # @property # def metric(self): diff --git a/python/cuvs/cuvs/test/test_cagra.py b/python/cuvs/cuvs/test/test_cagra.py index 6074eee3a7..a2b0184860 100644 --- a/python/cuvs/cuvs/test/test_cagra.py +++ b/python/cuvs/cuvs/test/test_cagra.py @@ -38,6 +38,7 @@ def run_cagra_build_search_test( inplace=True, add_data_on_build=True, search_params={}, + compression=None, ): dataset = generate_data((n_rows, n_cols), dtype) if metric == "inner_product": @@ -49,6 +50,7 @@ def run_cagra_build_search_test( intermediate_graph_degree=intermediate_graph_degree, graph_degree=graph_degree, build_algo=build_algo, + compression=compression, ) if array_type == "device": @@ -173,3 +175,11 @@ def test_cagra_index_params(params): compare=False, build_algo=params["build_algo"], ) + + +def test_cagra_vpq_compression(): + dim = 64 + pq_len = 2 + run_cagra_build_search_test( + n_cols=dim, compression=cagra.CompressionParams(pq_dim=dim / pq_len) + ) diff --git a/rust/cuvs/src/cagra/index.rs b/rust/cuvs/src/cagra/index.rs index 6a5149f073..959959f608 100644 --- a/rust/cuvs/src/cagra/index.rs +++ b/rust/cuvs/src/cagra/index.rs @@ -106,8 +106,7 @@ mod tests { use ndarray_rand::rand_distr::Uniform; use ndarray_rand::RandomExt; - #[test] - fn test_cagra_index() { + fn test_cagra(build_params: IndexParams) { let res = Resources::new().unwrap(); // Create a new random dataset to index @@ -117,7 +116,6 @@ mod tests { ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); // build the cagra index - let build_params = IndexParams::new().unwrap(); let index = Index::build(&res, &build_params, &dataset).expect("failed to create cagra index"); @@ -159,4 +157,18 @@ mod tests { assert_eq!(neighbors_host[[2, 0]], 2); assert_eq!(neighbors_host[[3, 0]], 3); } + + #[test] + fn test_cagra_index() { + let build_params = IndexParams::new().unwrap(); + test_cagra(build_params); + } + + #[test] + fn test_cagra_compression() { + use crate::cagra::CompressionParams; + let build_params = IndexParams::new().unwrap() + .set_compression(CompressionParams::new().unwrap()); + test_cagra(build_params); + } } diff --git a/rust/cuvs/src/cagra/index_params.rs b/rust/cuvs/src/cagra/index_params.rs index 2e3367e06e..9a481ef9e1 100644 --- a/rust/cuvs/src/cagra/index_params.rs +++ b/rust/cuvs/src/cagra/index_params.rs @@ -21,7 +21,79 @@ use std::io::{stderr, Write}; pub type BuildAlgo = ffi::cuvsCagraGraphBuildAlgo; /// Supplemental parameters to build CAGRA Index -pub struct IndexParams(pub ffi::cuvsCagraIndexParams_t); +pub struct CompressionParams(pub ffi::cuvsCagraCompressionParams_t); + +impl CompressionParams { + /// Returns a new CompressionParams + pub fn new() -> Result { + unsafe { + let mut params = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsCagraCompressionParamsCreate(params.as_mut_ptr()))?; + Ok(CompressionParams(params.assume_init())) + } + } + + /// The bit length of the vector element after compression by PQ. + pub fn set_pq_bits(self, pq_bits: u32) -> CompressionParams { + unsafe { + (*self.0).pq_bits = pq_bits; + } + self + } + + /// The dimensionality of the vector after compression by PQ. When zero, + /// an optimal value is selected using a heuristic. + pub fn set_pq_dim(self, pq_dim: u32) -> CompressionParams { + unsafe { + (*self.0).pq_dim = pq_dim; + } + self + } + + /// Vector Quantization (VQ) codebook size - number of "coarse cluster + /// centers". When zero, an optimal value is selected using a heuristic. + pub fn set_vq_n_centers(self, vq_n_centers: u32) -> CompressionParams { + unsafe { + (*self.0).vq_n_centers = vq_n_centers; + } + self + } + + /// The number of iterations searching for kmeans centers (both VQ & PQ + /// phases). + pub fn set_kmeans_n_iters(self, kmeans_n_iters: u32) -> CompressionParams { + unsafe { + (*self.0).kmeans_n_iters = kmeans_n_iters; + } + self + } + + /// The fraction of data to use during iterative kmeans building (VQ + /// phase). When zero, an optimal value is selected using a heuristic. + pub fn set_vq_kmeans_trainset_fraction( + self, + vq_kmeans_trainset_fraction: f64, + ) -> CompressionParams { + unsafe { + (*self.0).vq_kmeans_trainset_fraction = vq_kmeans_trainset_fraction; + } + self + } + + /// The fraction of data to use during iterative kmeans building (PQ + /// phase). When zero, an optimal value is selected using a heuristic. + pub fn set_pq_kmeans_trainset_fraction( + self, + pq_kmeans_trainset_fraction: f64, + ) -> CompressionParams { + unsafe { + (*self.0).pq_kmeans_trainset_fraction = pq_kmeans_trainset_fraction; + } + self + } +} + +pub struct IndexParams(pub ffi::cuvsCagraIndexParams_t, Option); impl IndexParams { /// Returns a new IndexParams @@ -29,7 +101,7 @@ impl IndexParams { unsafe { let mut params = std::mem::MaybeUninit::::uninit(); check_cuvs(ffi::cuvsCagraIndexParamsCreate(params.as_mut_ptr()))?; - Ok(IndexParams(params.assume_init())) + Ok(IndexParams(params.assume_init(), None)) } } @@ -64,6 +136,16 @@ impl IndexParams { } self } + + pub fn set_compression(mut self, compression: CompressionParams) -> IndexParams { + unsafe { + (*self.0).compression = compression.0; + } + // Note: we're moving the ownership of compression here to avoid having it cleaned up + // and leaving a dangling pointer + self.1 = Some(compression); + self + } } impl fmt::Debug for IndexParams { @@ -74,6 +156,12 @@ impl fmt::Debug for IndexParams { } } +impl fmt::Debug for CompressionParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "CompressionParams({:?})", unsafe { *self.0 }) + } +} + impl Drop for IndexParams { fn drop(&mut self) { if let Err(e) = check_cuvs(unsafe { ffi::cuvsCagraIndexParamsDestroy(self.0) }) { @@ -87,6 +175,19 @@ impl Drop for IndexParams { } } +impl Drop for CompressionParams { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsCagraCompressionParamsDestroy(self.0) }) { + write!( + stderr(), + "failed to call cuvsCagraCompressionParamsDestroy {:?}", + e + ) + .expect("failed to write to stderr"); + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -98,7 +199,13 @@ mod tests { .set_intermediate_graph_degree(128) .set_graph_degree(16) .set_build_algo(BuildAlgo::NN_DESCENT) - .set_nn_descent_niter(10); + .set_nn_descent_niter(10) + .set_compression( + CompressionParams::new() + .unwrap() + .set_pq_bits(4) + .set_pq_dim(8), + ); // make sure the setters actually updated internal representation on the c-struct unsafe { @@ -106,6 +213,8 @@ mod tests { assert_eq!((*params.0).intermediate_graph_degree, 128); assert_eq!((*params.0).build_algo, BuildAlgo::NN_DESCENT); assert_eq!((*params.0).nn_descent_niter, 10); + assert_eq!((*(*params.0).compression).pq_dim, 8); + assert_eq!((*(*params.0).compression).pq_bits, 4); } } } diff --git a/rust/cuvs/src/cagra/mod.rs b/rust/cuvs/src/cagra/mod.rs index 417ed9b0d8..c7db858421 100644 --- a/rust/cuvs/src/cagra/mod.rs +++ b/rust/cuvs/src/cagra/mod.rs @@ -82,5 +82,5 @@ mod index_params; mod search_params; pub use index::Index; -pub use index_params::{BuildAlgo, IndexParams}; +pub use index_params::{BuildAlgo, CompressionParams, IndexParams}; pub use search_params::{HashMode, SearchAlgo, SearchParams}; From 26377efa2cbd932c2d468a1fa3e58941b3eecdaa Mon Sep 17 00:00:00 2001 From: GALI PREM SAGAR Date: Wed, 10 Apr 2024 11:18:27 -0500 Subject: [PATCH 033/623] Enable all tests for `arm` jobs (#63) This PR enables all tests in `arm` jobs. xref: https://github.com/rapidsai/build-planning/issues/36 --------- Co-authored-by: Corey J. Nolet --- ci/test_wheel_cuvs.sh | 7 +------ ci/wheel_smoke_test_cuvs.py | 42 ------------------------------------- 2 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 ci/wheel_smoke_test_cuvs.py diff --git a/ci/test_wheel_cuvs.sh b/ci/test_wheel_cuvs.sh index 364e00a7cf..7033003e95 100755 --- a/ci/test_wheel_cuvs.sh +++ b/ci/test_wheel_cuvs.sh @@ -10,9 +10,4 @@ RAPIDS_PY_WHEEL_NAME="cuvs_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-from # echo to expand wildcard before adding `[extra]` requires for pip python -m pip install $(echo ./dist/cuvs*.whl)[test] -# Run smoke tests for aarch64 pull requests -if [[ "$(arch)" == "aarch64" && "${RAPIDS_BUILD_TYPE}" == "pull-request" ]]; then - python ./ci/wheel_smoke_test_cuvs.py -else - python -m pytest ./python/cuvs/cuvs/test -fi +python -m pytest ./python/cuvs/cuvs/test diff --git a/ci/wheel_smoke_test_cuvs.py b/ci/wheel_smoke_test_cuvs.py deleted file mode 100644 index 26ab5f6b50..0000000000 --- a/ci/wheel_smoke_test_cuvs.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2024, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import cupy as cp -import numpy as np - -from cuvs.neighbors import cagra -from pylibraft.common import Stream, DeviceResources - - -if __name__ == "__main__": - n_samples = 1000 - n_features = 50 - n_queries = 1000 - k = 10 - - dataset = cp.random.random_sample((n_samples, - n_features)).astype(cp.float32) - - build_params = cagra.IndexParams(metric="sqeuclidean", - build_algo="nn_descent") - - index = cagra.build_index(build_params, dataset) - - distances, neighbors = cagra.search(cagra.SearchParams(), - index, dataset, - k) - - distances = cp.asarray(distances) - neighbors = cp.asarray(neighbors) From 8976bd133d879d855796ee22ba0b86e02b7b250f Mon Sep 17 00:00:00 2001 From: Ray Douglass <3107146+raydouglass@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:34:07 -0400 Subject: [PATCH 034/623] Fix for 24.04 release (#75) Fixes some versions so `24.04` can be released. #69 implements the changes in the script This PR should replace #76 --------- Co-authored-by: Jake Awe Co-authored-by: Corey J. Nolet Co-authored-by: ptaylor --- .../cuda11.8-conda/devcontainer.json | 4 ++-- .devcontainer/cuda11.8-pip/devcontainer.json | 16 +++++++++++--- .../devcontainer.json | 8 +++---- .../devcontainer.json | 22 ++++++++++++++----- .github/workflows/build.yaml | 2 +- .github/workflows/pr.yaml | 6 +++-- python/cuvs/CMakeLists.txt | 2 +- python/cuvs/cuvs/common/CMakeLists.txt | 2 +- .../cuvs/neighbors/brute_force/CMakeLists.txt | 2 +- .../cuvs/cuvs/neighbors/cagra/CMakeLists.txt | 2 +- 10 files changed, 44 insertions(+), 22 deletions(-) rename .devcontainer/{cuda12.0-conda => cuda12.2-conda}/devcontainer.json (92%) rename .devcontainer/{cuda12.0-pip => cuda12.2-pip}/devcontainer.json (72%) diff --git a/.devcontainer/cuda11.8-conda/devcontainer.json b/.devcontainer/cuda11.8-conda/devcontainer.json index cefbea72b7..007ff83b56 100644 --- a/.devcontainer/cuda11.8-conda/devcontainer.json +++ b/.devcontainer/cuda11.8-conda/devcontainer.json @@ -5,12 +5,12 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "conda", - "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda11.8-mambaforge-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.04-cpp-cuda11.8-mambaforge-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.4": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" diff --git a/.devcontainer/cuda11.8-pip/devcontainer.json b/.devcontainer/cuda11.8-pip/devcontainer.json index 05518805aa..aae6b0f953 100644 --- a/.devcontainer/cuda11.8-pip/devcontainer.json +++ b/.devcontainer/cuda11.8-pip/devcontainer.json @@ -5,16 +5,26 @@ "args": { "CUDA": "11.8", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda11.8-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.04-cpp-cuda11.8-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/ucx:24.2": {"version": "1.14.1"}, - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/ucx:24.4": { + "version": "1.14.1" + }, + "ghcr.io/rapidsai/devcontainers/features/cuda:24.4": { + "version": "11.8", + "installcuBLAS": true, + "installcuSOLVER": true, + "installcuRAND": true, + "installcuSPARSE": true + }, + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.4": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/ucx", + "ghcr.io/rapidsai/devcontainers/features/cuda", "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" ], "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda11.8-venvs}"], diff --git a/.devcontainer/cuda12.0-conda/devcontainer.json b/.devcontainer/cuda12.2-conda/devcontainer.json similarity index 92% rename from .devcontainer/cuda12.0-conda/devcontainer.json rename to .devcontainer/cuda12.2-conda/devcontainer.json index 3f89836e26..4349cd80a1 100644 --- a/.devcontainer/cuda12.0-conda/devcontainer.json +++ b/.devcontainer/cuda12.2-conda/devcontainer.json @@ -3,19 +3,19 @@ "context": "${localWorkspaceFolder}/.devcontainer", "dockerfile": "${localWorkspaceFolder}/.devcontainer/Dockerfile", "args": { - "CUDA": "12.0", + "CUDA": "12.2", "PYTHON_PACKAGE_MANAGER": "conda", "BASE": "rapidsai/devcontainers:24.04-cpp-mambaforge-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.4": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" ], - "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config,conda/pkgs,conda/${localWorkspaceFolderBasename}-cuda12.0-envs}"], + "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config,conda/pkgs,conda/${localWorkspaceFolderBasename}-cuda12.2-envs}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; . rapids-post-attach-command; fi"], "workspaceFolder": "/home/coder", "workspaceMount": "source=${localWorkspaceFolder},target=/home/coder/cuvs,type=bind,consistency=consistent", @@ -24,7 +24,7 @@ "source=${localWorkspaceFolder}/../.cache,target=/home/coder/.cache,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.config,target=/home/coder/.config,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.conda/pkgs,target=/home/coder/.conda/pkgs,type=bind,consistency=consistent", - "source=${localWorkspaceFolder}/../.conda/${localWorkspaceFolderBasename}-cuda12.0-envs,target=/home/coder/.conda/envs,type=bind,consistency=consistent" + "source=${localWorkspaceFolder}/../.conda/${localWorkspaceFolderBasename}-cuda12.2-envs,target=/home/coder/.conda/envs,type=bind,consistency=consistent" ], "customizations": { "vscode": { diff --git a/.devcontainer/cuda12.0-pip/devcontainer.json b/.devcontainer/cuda12.2-pip/devcontainer.json similarity index 72% rename from .devcontainer/cuda12.0-pip/devcontainer.json rename to .devcontainer/cuda12.2-pip/devcontainer.json index 33f67cd3fb..a8a8fdb5b8 100644 --- a/.devcontainer/cuda12.0-pip/devcontainer.json +++ b/.devcontainer/cuda12.2-pip/devcontainer.json @@ -3,21 +3,31 @@ "context": "${localWorkspaceFolder}/.devcontainer", "dockerfile": "${localWorkspaceFolder}/.devcontainer/Dockerfile", "args": { - "CUDA": "12.0", + "CUDA": "12.2", "PYTHON_PACKAGE_MANAGER": "pip", - "BASE": "rapidsai/devcontainers:24.04-cpp-llvm16-cuda12.0-ubuntu22.04" + "BASE": "rapidsai/devcontainers:24.04-cpp-cuda12.2-ubuntu22.04" } }, "hostRequirements": {"gpu": "optional"}, "features": { - "ghcr.io/rapidsai/devcontainers/features/ucx:24.2": {"version": "1.14.1"}, - "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.2": {} + "ghcr.io/rapidsai/devcontainers/features/ucx:24.4": { + "version": "1.14.1" + }, + "ghcr.io/rapidsai/devcontainers/features/cuda:24.4": { + "version": "12.2", + "installcuBLAS": true, + "installcuSOLVER": true, + "installcuRAND": true, + "installcuSPARSE": true + }, + "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.4": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/rapidsai/devcontainers/features/ucx", + "ghcr.io/rapidsai/devcontainers/features/cuda", "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils" ], - "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda12.0-venvs}"], + "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda12.2-venvs}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; . rapids-post-attach-command; fi"], "workspaceFolder": "/home/coder", "workspaceMount": "source=${localWorkspaceFolder},target=/home/coder/cuvs,type=bind,consistency=consistent", @@ -25,7 +35,7 @@ "source=${localWorkspaceFolder}/../.aws,target=/home/coder/.aws,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.cache,target=/home/coder/.cache,type=bind,consistency=consistent", "source=${localWorkspaceFolder}/../.config,target=/home/coder/.config,type=bind,consistency=consistent", - "source=${localWorkspaceFolder}/../.local/share/${localWorkspaceFolderBasename}-cuda12.0-venvs,target=/home/coder/.local/share/venvs,type=bind,consistency=consistent" + "source=${localWorkspaceFolder}/../.local/share/${localWorkspaceFolderBasename}-cuda12.2-venvs,target=/home/coder/.local/share/venvs,type=bind,consistency=consistent" ], "customizations": { "vscode": { diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 61ebdf0bca..62f648d7e3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -37,7 +37,7 @@ jobs: rust-build: needs: cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.04 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 7c6db2c603..c87712a4f8 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -76,7 +76,7 @@ jobs: rust-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.04 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -101,7 +101,9 @@ jobs: secrets: inherit uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@branch-24.04 with: + arch: '["amd64"]' + cuda: '["12.2"]' build_command: | sccache -z; - build-all -DBUILD_PRIMS_BENCH=ON -DBUILD_ANN_BENCH=ON --verbose; + build-all --verbose; sccache -s; diff --git a/python/cuvs/CMakeLists.txt b/python/cuvs/CMakeLists.txt index 36a62044ae..b2a65797e5 100644 --- a/python/cuvs/CMakeLists.txt +++ b/python/cuvs/CMakeLists.txt @@ -53,7 +53,7 @@ rapids_cpm_init() # If the user requested it we attempt to find CUVS. if(FIND_CUVS_CPP) - find_package(cuvs "${RAPIDS_VERSION}" REQUIRED) + find_package(cuvs "${RAPIDS_VERSION}" REQUIRED COMPONENTS c_api) include(../../cpp/cmake/thirdparty/get_dlpack.cmake) else() set(cuvs_FOUND OFF) diff --git a/python/cuvs/cuvs/common/CMakeLists.txt b/python/cuvs/cuvs/common/CMakeLists.txt index 066a73d351..202919e012 100644 --- a/python/cuvs/cuvs/common/CMakeLists.txt +++ b/python/cuvs/cuvs/common/CMakeLists.txt @@ -14,7 +14,7 @@ # Set the list of Cython files to build set(cython_sources cydlpack.pyx exceptions.pyx resources.pyx) -set(linked_libraries cuvs::cuvs cuvs_c) +set(linked_libraries cuvs::cuvs cuvs::c_api) # Build all of the Cython targets rapids_cython_create_modules( diff --git a/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt b/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt index 8534370aee..4806fb9fc0 100644 --- a/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt +++ b/python/cuvs/cuvs/neighbors/brute_force/CMakeLists.txt @@ -14,7 +14,7 @@ # Set the list of Cython files to build set(cython_sources brute_force.pyx) -set(linked_libraries cuvs::cuvs cuvs_c) +set(linked_libraries cuvs::cuvs cuvs::c_api) # Build all of the Cython targets rapids_cython_create_modules( diff --git a/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt b/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt index 377cfe7793..87e6597fe1 100644 --- a/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt +++ b/python/cuvs/cuvs/neighbors/cagra/CMakeLists.txt @@ -14,7 +14,7 @@ # Set the list of Cython files to build set(cython_sources cagra.pyx) -set(linked_libraries cuvs::cuvs cuvs_c) +set(linked_libraries cuvs::cuvs cuvs::c_api) # Build all of the Cython targets rapids_cython_create_modules( From 8df8c49e1ab24b7754e26211467f9e264933e06e Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Mon, 15 Apr 2024 15:49:05 -0400 Subject: [PATCH 035/623] Update the developer's guide with new copyright hook (#81) Issue: https://github.com/rapidsai/build-planning/issues/30 Authors: - Kyle Edwards (https://github.com/KyleFromNVIDIA) Approvers: - Ben Frederickson (https://github.com/benfred) URL: https://github.com/rapidsai/cuvs/pull/81 --- docs/source/developer_guide.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/source/developer_guide.md b/docs/source/developer_guide.md index e10e8987af..5e288e9f2f 100644 --- a/docs/source/developer_guide.md +++ b/docs/source/developer_guide.md @@ -215,13 +215,14 @@ python ./cpp/scripts/include_checker.py --inplace [cpp/include cpp/test ... list ``` ### Copyright header -[copyright.py](https://github.com/rapidsai/raft/blob/branch-24.06/ci/checks/copyright.py) checks the Copyright header for all git-modified files +RAPIDS [pre-commit-hooks](https://github.com/rapidsai/pre-commit-hooks) checks the Copyright +header for all git-modified files. -Manually, you can run the following to bulk-fix the header if only the years need to be updated: +Manually, you can run the following to bulk-fix the header on all files in the repository: ```bash -python ./ci/checks/copyright.py --update-current-year +pre-commit run -a verify-copyright ``` -Keep in mind that this only applies to files tracked by git and having been modified. +Keep in mind that this only applies to files tracked by git that have been modified. ## Error handling Call CUDA APIs via the provided helper macros `RAFT_CUDA_TRY`, `RAFT_CUBLAS_TRY` and `RAFT_CUSOLVER_TRY`. These macros take care of checking the return values of the used API calls and generate an exception when the command is not successful. If you need to avoid an exception, e.g. inside a destructor, use `RAFT_CUDA_TRY_NO_THROW`, `RAFT_CUBLAS_TRY_NO_THROW ` and `RAFT_CUSOLVER_TRY_NO_THROW`. These macros log the error but do not throw an exception. From a23704989484980499cffaffceed7f5d2a404cf8 Mon Sep 17 00:00:00 2001 From: Robert Maynard Date: Tue, 16 Apr 2024 10:09:15 -0400 Subject: [PATCH 036/623] Use static gtest (#83) Allows us to remove the gtest dependency from our conda env for testing. Authors: - Robert Maynard (https://github.com/robertmaynard) Approvers: - Bradley Dice (https://github.com/bdice) - Jake Awe (https://github.com/AyodeAwe) - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/83 --- conda/recipes/libcuvs/conda_build_config.yaml | 3 --- conda/recipes/libcuvs/meta.yaml | 4 ---- cpp/CMakeLists.txt | 3 ++- cpp/cmake/thirdparty/get_gtest.cmake | 22 ------------------- 4 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 cpp/cmake/thirdparty/get_gtest.cmake diff --git a/conda/recipes/libcuvs/conda_build_config.yaml b/conda/recipes/libcuvs/conda_build_config.yaml index 73c3281e66..c833f68ba7 100644 --- a/conda/recipes/libcuvs/conda_build_config.yaml +++ b/conda/recipes/libcuvs/conda_build_config.yaml @@ -19,9 +19,6 @@ cmake_version: nccl_version: - ">=2.9.9" -gtest_version: - - ">=1.13.0" - h5py_version: - ">=3.8.0" diff --git a/conda/recipes/libcuvs/meta.yaml b/conda/recipes/libcuvs/meta.yaml index 4ad3618c38..7a09015ccc 100644 --- a/conda/recipes/libcuvs/meta.yaml +++ b/conda/recipes/libcuvs/meta.yaml @@ -216,8 +216,6 @@ outputs: - libcusolver-dev - libcusparse-dev {% endif %} - - gmock {{ gtest_version }} - - gtest {{ gtest_version }} run: - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} {% if cuda_major == "11" %} @@ -231,8 +229,6 @@ outputs: {% endif %} - libraft ={{ minor_version }} - {{ pin_subpackage('libcuvs', exact=True) }} - - gmock {{ gtest_version }} - - gtest {{ gtest_version }} about: home: https://rapids.ai/ license: Apache-2.0 diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c2d311bf23..eb9de7a9db 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -177,7 +177,8 @@ if(BUILD_C_LIBRARY) endif() if(BUILD_TESTS OR BUILD_C_TESTS) - include(cmake/thirdparty/get_gtest.cmake) + include(${rapids-cmake-dir}/cpm/gtest.cmake) + rapids_cpm_gtest(BUILD_STATIC) endif() include(cmake/thirdparty/get_cutlass.cmake) diff --git a/cpp/cmake/thirdparty/get_gtest.cmake b/cpp/cmake/thirdparty/get_gtest.cmake deleted file mode 100644 index 34fca4c7dc..0000000000 --- a/cpp/cmake/thirdparty/get_gtest.cmake +++ /dev/null @@ -1,22 +0,0 @@ -#============================================================================= -# Copyright (c) 2021-2023, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#============================================================================= - -function(find_and_configure_gtest ) - include(${rapids-cmake-dir}/cpm/gtest.cmake) - rapids_cpm_gtest() -endfunction() - -find_and_configure_gtest() From da1e63bab83ee9fd366abd1a746fb23afe08a91f Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Thu, 18 Apr 2024 10:12:09 -0700 Subject: [PATCH 037/623] Fix rust docs build (#84) The rust API docs aren't being generated on docs.rapids.ai/cuvs . While the `build.sh docs` script was including the rust api docs, the `ci/build_docs.sh` wasn't. Fix. Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/84 --- ci/build_docs.sh | 7 +++++++ dependencies.yaml | 1 + 2 files changed, 8 insertions(+) diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 9d4509e08a..fbf24b346d 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -40,10 +40,17 @@ pushd cpp/doxygen doxygen Doxyfile popd +rapids-logger "Build Rust docs" +pushd rust +export LIBCLANG_PATH=$(dirname $(find /opt/conda -name libclang.so | head -n 1)) +cargo doc -p cuvs --no-deps +popd + rapids-logger "Build Python docs" pushd docs sphinx-build -b dirhtml source _html sphinx-build -b text source _text +mv ../rust/target/doc ./_html/_static/rust mkdir -p "${RAPIDS_DOCS_DIR}/cuvs/"{html,txt} mv _html/* "${RAPIDS_DOCS_DIR}/cuvs/html" mv _text/* "${RAPIDS_DOCS_DIR}/cuvs/txt" diff --git a/dependencies.yaml b/dependencies.yaml index 1951ee5cf0..74b573c017 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -47,6 +47,7 @@ files: - py_version - rust - build + - cuda rust: output: none includes: From aea7f5423fb78db2258442c208e2641298b6f399 Mon Sep 17 00:00:00 2001 From: Paul Taylor <178183+trxcllnt@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:24:22 -0700 Subject: [PATCH 038/623] Update pip devcontainers to UCX v1.15.0 (#88) Authors: - Paul Taylor (https://github.com/trxcllnt) Approvers: - Ben Frederickson (https://github.com/benfred) URL: https://github.com/rapidsai/cuvs/pull/88 --- .devcontainer/cuda11.8-pip/devcontainer.json | 2 +- .devcontainer/cuda12.2-pip/devcontainer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/cuda11.8-pip/devcontainer.json b/.devcontainer/cuda11.8-pip/devcontainer.json index f34a6adc84..05f45a39a1 100644 --- a/.devcontainer/cuda11.8-pip/devcontainer.json +++ b/.devcontainer/cuda11.8-pip/devcontainer.json @@ -11,7 +11,7 @@ "hostRequirements": {"gpu": "optional"}, "features": { "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": { - "version": "1.14.1" + "version": "1.15.0" }, "ghcr.io/rapidsai/devcontainers/features/cuda:24.6": { "version": "11.8", diff --git a/.devcontainer/cuda12.2-pip/devcontainer.json b/.devcontainer/cuda12.2-pip/devcontainer.json index 8dd06ae6c4..7d57303497 100644 --- a/.devcontainer/cuda12.2-pip/devcontainer.json +++ b/.devcontainer/cuda12.2-pip/devcontainer.json @@ -11,7 +11,7 @@ "hostRequirements": {"gpu": "optional"}, "features": { "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": { - "version": "1.14.1" + "version": "1.15.0" }, "ghcr.io/rapidsai/devcontainers/features/cuda:24.6": { "version": "12.2", From 5ff588dd1e3ddc8462026294918d85bc0a4b6d9f Mon Sep 17 00:00:00 2001 From: Shunya Ueta Date: Wed, 24 Apr 2024 02:50:24 +0900 Subject: [PATCH 039/623] update: fix RAFT URL in README (#91) ## What SSIA ## Why Maintain Docs Authors: - Shunya Ueta (https://github.com/hurutoriya) Approvers: - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/91 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c82294f570..6829c80f72 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ #
 cuVS: Vector Search and Clustering on the GPU
> [!note] -> cuVS is a new library mostly derived from the approximate nearest neighbors and clustering algorithms in the [RAPIDS RAFT](https://github.com/rapidsai) library of data mining primitives. RAPIDS RAFT currently contains the most fully-featured versions of the approximate nearest neighbors and clustering algorithms in cuVS. We are in the process of migrating the algorithms from RAFT to cuVS, but if you are unsure of which to use, please consider the following: +> cuVS is a new library mostly derived from the approximate nearest neighbors and clustering algorithms in the [RAPIDS RAFT](https://github.com/rapidsai/raft) library of data mining primitives. RAPIDS RAFT currently contains the most fully-featured versions of the approximate nearest neighbors and clustering algorithms in cuVS. We are in the process of migrating the algorithms from RAFT to cuVS, but if you are unsure of which to use, please consider the following: > 1. RAFT contains C++ and Python APIs for all of the approximate nearest neighbors and clustering algorithms. > 2. cuVS contains a growing support for different languages, including C, C++, Python, and Rust. We will be adding more language support to cuVS in the future but will not be improving the language support for RAFT. > 3. Once all of RAFT's approximate nearest neighbors and clustering algorithms are moved to cuVS, the RAFT APIs will be deprecated and eventually removed altogether. Once removed, RAFT will become a lightweight header-only library. In the meantime, there's no harm in using RAFT if support for additional languages is not needed. From b38dc11f0e4e1ec23356bced325079ca467037a1 Mon Sep 17 00:00:00 2001 From: Robert Maynard Date: Tue, 23 Apr 2024 22:44:06 -0400 Subject: [PATCH 040/623] Remove gtest from dependencies.yaml (#87) Missed these files in https://github.com/rapidsai/cuvs/pull/83 Authors: - Robert Maynard (https://github.com/robertmaynard) Approvers: - Corey J. Nolet (https://github.com/cjnolet) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/87 --- conda/environments/all_cuda-118_arch-aarch64.yaml | 2 -- conda/environments/all_cuda-118_arch-x86_64.yaml | 2 -- conda/environments/all_cuda-122_arch-aarch64.yaml | 2 -- conda/environments/all_cuda-122_arch-x86_64.yaml | 2 -- dependencies.yaml | 2 -- 5 files changed, 10 deletions(-) diff --git a/conda/environments/all_cuda-118_arch-aarch64.yaml b/conda/environments/all_cuda-118_arch-aarch64.yaml index 364d9c76af..d1a6fa011e 100644 --- a/conda/environments/all_cuda-118_arch-aarch64.yaml +++ b/conda/environments/all_cuda-118_arch-aarch64.yaml @@ -24,9 +24,7 @@ dependencies: - dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-aarch64=11.* -- gmock>=1.13.0 - graphviz -- gtest>=1.13.0 - ipython - libclang - libcublas-dev=11.11.3.6 diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index f269ff55ea..e604ed8f31 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -24,9 +24,7 @@ dependencies: - dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-64=11.* -- gmock>=1.13.0 - graphviz -- gtest>=1.13.0 - ipython - libclang - libcublas-dev=11.11.3.6 diff --git a/conda/environments/all_cuda-122_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml index d232a16318..7b50ceb0a0 100644 --- a/conda/environments/all_cuda-122_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -25,9 +25,7 @@ dependencies: - dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-aarch64=11.* -- gmock>=1.13.0 - graphviz -- gtest>=1.13.0 - ipython - libclang - libcublas-dev diff --git a/conda/environments/all_cuda-122_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml index a5dc50d762..6c933e193d 100644 --- a/conda/environments/all_cuda-122_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -25,9 +25,7 @@ dependencies: - dlpack>=0.8,<1.0 - doxygen>=1.8.20 - gcc_linux-64=11.* -- gmock>=1.13.0 - graphviz -- gtest>=1.13.0 - ipython - libclang - libcublas-dev diff --git a/dependencies.yaml b/dependencies.yaml index 74b573c017..eb1f99d4a1 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -304,8 +304,6 @@ dependencies: - output_types: [conda] packages: - *cmake_ver - - gtest>=1.13.0 - - gmock>=1.13.0 docs: common: - output_types: [conda] From 32235a4ef22919ea57c3a9b2c4345be2fc3dd2aa Mon Sep 17 00:00:00 2001 From: Paul Taylor <178183+trxcllnt@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:31:37 -0700 Subject: [PATCH 041/623] add --rm and --name to devcontainer run args (#89) * Remove the devcontainer when the VSCode window closes * Adds a descriptive name to the running container: ```shell $ docker ps -a CONTAINER ID IMAGE ... NAMES 0dbb364fe544 vsc-cuvs-... ... rapids-cuvs-24.06-cuda12.2-conda $ docker rm -f rapids-cuvs-24.06-cuda12.2-conda ``` Authors: - Paul Taylor (https://github.com/trxcllnt) Approvers: - Ben Frederickson (https://github.com/benfred) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cuvs/pull/89 --- .devcontainer/cuda11.8-conda/devcontainer.json | 5 +++++ .devcontainer/cuda11.8-pip/devcontainer.json | 5 +++++ .devcontainer/cuda12.2-conda/devcontainer.json | 5 +++++ .devcontainer/cuda12.2-pip/devcontainer.json | 5 +++++ ci/release/update-version.sh | 1 + 5 files changed, 21 insertions(+) diff --git a/.devcontainer/cuda11.8-conda/devcontainer.json b/.devcontainer/cuda11.8-conda/devcontainer.json index 72fbef26d6..bfba756fbe 100644 --- a/.devcontainer/cuda11.8-conda/devcontainer.json +++ b/.devcontainer/cuda11.8-conda/devcontainer.json @@ -8,6 +8,11 @@ "BASE": "rapidsai/devcontainers:24.06-cpp-cuda11.8-mambaforge-ubuntu22.04" } }, + "runArgs": [ + "--rm", + "--name", + "${localEnv:USER}-rapids-${localWorkspaceFolderBasename}-24.06-cuda11.8-conda" + ], "hostRequirements": {"gpu": "optional"}, "features": { "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} diff --git a/.devcontainer/cuda11.8-pip/devcontainer.json b/.devcontainer/cuda11.8-pip/devcontainer.json index 05f45a39a1..bb25e2ba31 100644 --- a/.devcontainer/cuda11.8-pip/devcontainer.json +++ b/.devcontainer/cuda11.8-pip/devcontainer.json @@ -8,6 +8,11 @@ "BASE": "rapidsai/devcontainers:24.06-cpp-cuda11.8-ubuntu22.04" } }, + "runArgs": [ + "--rm", + "--name", + "${localEnv:USER}-rapids-${localWorkspaceFolderBasename}-24.06-cuda11.8-pip" + ], "hostRequirements": {"gpu": "optional"}, "features": { "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": { diff --git a/.devcontainer/cuda12.2-conda/devcontainer.json b/.devcontainer/cuda12.2-conda/devcontainer.json index d6b7796cb7..bc0548622b 100644 --- a/.devcontainer/cuda12.2-conda/devcontainer.json +++ b/.devcontainer/cuda12.2-conda/devcontainer.json @@ -8,6 +8,11 @@ "BASE": "rapidsai/devcontainers:24.06-cpp-mambaforge-ubuntu22.04" } }, + "runArgs": [ + "--rm", + "--name", + "${localEnv:USER}-rapids-${localWorkspaceFolderBasename}-24.06-cuda12.2-conda" + ], "hostRequirements": {"gpu": "optional"}, "features": { "ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:24.6": {} diff --git a/.devcontainer/cuda12.2-pip/devcontainer.json b/.devcontainer/cuda12.2-pip/devcontainer.json index 7d57303497..19ab3e145e 100644 --- a/.devcontainer/cuda12.2-pip/devcontainer.json +++ b/.devcontainer/cuda12.2-pip/devcontainer.json @@ -8,6 +8,11 @@ "BASE": "rapidsai/devcontainers:24.06-cpp-cuda12.2-ubuntu22.04" } }, + "runArgs": [ + "--rm", + "--name", + "${localEnv:USER}-rapids-${localWorkspaceFolderBasename}-24.06-cuda12.2-pip" + ], "hostRequirements": {"gpu": "optional"}, "features": { "ghcr.io/rapidsai/devcontainers/features/ucx:24.6": { diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index 589110ab12..b8f15aba78 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -91,4 +91,5 @@ find .devcontainer/ -type f -name devcontainer.json -print0 | while IFS= read -r sed_runner "s@rapidsai/devcontainers:[0-9.]*@rapidsai/devcontainers:${NEXT_SHORT_TAG}@g" "${filename}" sed_runner "s@rapidsai/devcontainers/features/ucx:[0-9.]*@rapidsai/devcontainers/features/ucx:${NEXT_SHORT_TAG_PEP440}@" "${filename}" sed_runner "s@rapidsai/devcontainers/features/rapids-build-utils:[0-9.]*@rapidsai/devcontainers/features/rapids-build-utils:${NEXT_SHORT_TAG_PEP440}@" "${filename}" + sed_runner "s@rapids-\${localWorkspaceFolderBasename}-${CURRENT_SHORT_TAG}@rapids-\${localWorkspaceFolderBasename}-${NEXT_SHORT_TAG}@g" "${filename}" done From 8db4638ff376c195df48022f647321b258048bd6 Mon Sep 17 00:00:00 2001 From: "Corey J. Nolet" Date: Mon, 29 Apr 2024 15:09:15 -0400 Subject: [PATCH 042/623] Moving and renaming distance namespaces from raft -> cuvs (#85) This PR moves the `raft::distance` APIs over to cuVS. We're ripping the band-aid off quickly here so moving all over at once. All of the non-public APIs are being moved to `src/distance` and only the public APIs being moved to `include/cuvs/distance`. Authors: - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Ben Frederickson (https://github.com/benfred) - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cuvs/pull/85 --- .../all_cuda-122_arch-aarch64.yaml | 1 + .../all_cuda-122_arch-x86_64.yaml | 1 + cpp/CMakeLists.txt | 91 +- cpp/include/cuvs/distance/distance_types.h | 4 +- cpp/include/cuvs/distance/distance_types.hpp | 48 +- .../cuvs/distance/pairwise_distance.hpp | 206 ++++ cpp/include/cuvs/neighbors/brute_force.h | 2 +- cpp/include/cuvs/neighbors/ivf_flat.h | 2 +- cpp/include/cuvs/neighbors/ivf_pq.h | 2 +- cpp/src/distance/detail/compress_to_bits.cuh | 123 ++ cpp/src/distance/detail/distance.cuh | 815 +++++++++++++ .../distance/detail/distance_ops/all_ops.cuh | 35 + .../distance/detail/distance_ops/canberra.cuh | 71 ++ .../detail/distance_ops/correlation.cuh | 126 ++ .../distance/detail/distance_ops/cosine.cuh | 85 ++ .../distance/detail/distance_ops/cutlass.cuh | 40 + .../distance/detail/distance_ops/hamming.cuh | 73 ++ .../detail/distance_ops/hellinger.cuh | 77 ++ .../detail/distance_ops/jensen_shannon.cuh | 81 ++ .../detail/distance_ops/kl_divergence.cuh | 99 ++ cpp/src/distance/detail/distance_ops/l1.cuh | 62 + .../distance/detail/distance_ops/l2_exp.cuh | 136 +++ .../distance/detail/distance_ops/l2_unexp.cuh | 79 ++ .../distance/detail/distance_ops/l_inf.cuh | 67 ++ .../distance/detail/distance_ops/lp_unexp.cuh | 78 ++ .../detail/distance_ops/russel_rao.cuh | 74 ++ .../distance/detail/distance_ops/template.cuh | 68 ++ cpp/src/distance/detail/fused_distance_nn.cuh | 97 ++ .../custom_epilogue_with_broadcast.h | 668 +++++++++++ .../detail/fused_distance_nn/cutlass_base.cuh | 177 +++ .../detail/fused_distance_nn/epilogue.cuh | 134 +++ .../epilogue_elementwise.cuh | 220 ++++ .../fused_distance_nn/fused_cosine_nn.cuh | 136 +++ .../detail/fused_distance_nn/fused_l2_nn.cuh | 146 +++ .../distance/detail/fused_distance_nn/gemm.h | 409 +++++++ .../fused_distance_nn/helper_structs.cuh | 146 +++ .../fused_distance_nn/persistent_gemm.h | 512 ++++++++ .../predicated_tile_iterator_normvec_smem.h | 448 +++++++ .../predicated_tile_iterator_reduced_vec.h | 610 ++++++++++ .../detail/fused_distance_nn/simt_kernel.cuh | 187 +++ cpp/src/distance/detail/fused_l2_nn.cuh | 386 ++++++ .../distance/detail/kernels/gram_matrix.cuh | 488 ++++++++ .../detail/kernels/kernel_factory.cuh | 65 + .../detail/kernels/kernel_matrices.cuh | 777 ++++++++++++ .../distance/detail/kernels/rbf_fin_op.cuh | 51 + .../distance/detail/masked_distance_base.cuh | 326 +++++ cpp/src/distance/detail/masked_nn.cuh | 329 +++++ .../detail/pairwise_distance_base.cuh | 326 +++++ .../detail/pairwise_distance_cutlass_base.cuh | 185 +++ .../detail/pairwise_distance_epilogue.h | 100 ++ .../pairwise_distance_epilogue_elementwise.h | 171 +++ .../distance/detail/pairwise_distance_gemm.h | 238 ++++ .../detail/pairwise_matrix/dispatch-ext.cuh | 194 +++ .../detail/pairwise_matrix/dispatch-inl.cuh | 127 ++ .../detail/pairwise_matrix/dispatch.cuh | 24 + .../pairwise_matrix/dispatch_00_generate.py | 194 +++ ...patch_canberra_double_double_double_int.cu | 55 + ...dispatch_canberra_float_float_float_int.cu | 50 + ...ch_correlation_double_double_double_int.cu | 55 + ...patch_correlation_float_float_float_int.cu | 55 + ...ispatch_cosine_double_double_double_int.cu | 51 + .../dispatch_cosine_float_float_float_int.cu | 51 + ...ing_unexpanded_double_double_double_int.cu | 50 + ...amming_unexpanded_float_float_float_int.cu | 50 + ...inger_expanded_double_double_double_int.cu | 55 + ...ellinger_expanded_float_float_float_int.cu | 50 + ...jensen_shannon_double_double_double_int.cu | 55 + ...ch_jensen_shannon_float_float_float_int.cu | 55 + ..._kl_divergence_double_double_double_int.cu | 50 + ...tch_kl_divergence_float_float_float_int.cu | 50 + .../dispatch_l1_double_double_double_int.cu | 51 + .../dispatch_l1_float_float_float_int.cu | 50 + ...ch_l2_expanded_double_double_double_int.cu | 51 + ...patch_l2_expanded_float_float_float_int.cu | 51 + ..._l2_unexpanded_double_double_double_int.cu | 55 + ...tch_l2_unexpanded_float_float_float_int.cu | 50 + ...dispatch_l_inf_double_double_double_int.cu | 50 + .../dispatch_l_inf_float_float_float_int.cu | 50 + .../pairwise_matrix/dispatch_layout.cuh | 117 ++ ..._lp_unexpanded_double_double_double_int.cu | 55 + ...tch_lp_unexpanded_float_float_float_int.cu | 50 + .../detail/pairwise_matrix/dispatch_rbf.cu | 64 + ...tch_russel_rao_double_double_double_int.cu | 55 + ...spatch_russel_rao_float_float_float_int.cu | 50 + .../detail/pairwise_matrix/dispatch_sm60.cuh | 85 ++ .../detail/pairwise_matrix/dispatch_sm80.cuh | 69 ++ .../detail/pairwise_matrix/kernel_sm60.cuh | 156 +++ .../detail/pairwise_matrix/params.cuh | 47 + .../detail/predicated_tile_iterator_normvec.h | 585 +++++++++ cpp/src/distance/distance-ext.cuh | 1066 +++++++++++++++++ cpp/src/distance/distance-inl.cuh | 478 ++++++++ cpp/src/distance/distance.cu | 934 +++++++++++++++ cpp/src/distance/distance.cuh | 24 + cpp/src/distance/pairwise_distance.cu | 103 ++ cpp/src/neighbors/brute_force_c.cpp | 4 +- cpp/src/neighbors/cagra_build_float.cpp | 2 +- cpp/test/CMakeLists.txt | 42 +- cpp/test/distance/dist_canberra.cu | 70 ++ cpp/test/distance/dist_correlation.cu | 94 ++ cpp/test/distance/dist_cos.cu | 112 ++ cpp/test/distance/dist_hamming.cu | 71 ++ cpp/test/distance/dist_hellinger.cu | 71 ++ cpp/test/distance/dist_inner_product.cu | 74 ++ cpp/test/distance/dist_jensen_shannon.cu | 71 ++ cpp/test/distance/dist_kl_divergence.cu | 71 ++ cpp/test/distance/dist_l1.cu | 70 ++ cpp/test/distance/dist_l2_exp.cu | 115 ++ cpp/test/distance/dist_l2_sqrt_exp.cu | 74 ++ cpp/test/distance/dist_l2_unexp.cu | 71 ++ cpp/test/distance/dist_l_inf.cu | 70 ++ cpp/test/distance/dist_lp_unexp.cu | 71 ++ cpp/test/distance/dist_russell_rao.cu | 71 ++ cpp/test/distance/distance_base.cuh | 710 +++++++++++ cpp/test/neighbors/ann_ivf_flat_c.cu | 10 +- cpp/test/neighbors/ann_ivf_pq_c.cu | 10 +- cpp/test/neighbors/ann_utils.cuh | 50 +- cpp/test/neighbors/brute_force_c.cu | 6 +- cpp/test/neighbors/run_brute_force_c.c | 2 +- cpp/test/neighbors/run_ivf_flat_c.c | 2 +- cpp/test/neighbors/run_ivf_pq_c.c | 2 +- dependencies.yaml | 1 + docs/source/cpp_api/distance.rst | 13 + python/cuvs/cuvs/distance_type.pxd | 2 +- .../neighbors/brute_force/brute_force.pxd | 4 +- .../neighbors/brute_force/brute_force.pyx | 4 +- rust/cuvs/src/distance_type.rs | 2 +- 126 files changed, 17390 insertions(+), 147 deletions(-) create mode 100644 cpp/include/cuvs/distance/pairwise_distance.hpp create mode 100644 cpp/src/distance/detail/compress_to_bits.cuh create mode 100644 cpp/src/distance/detail/distance.cuh create mode 100644 cpp/src/distance/detail/distance_ops/all_ops.cuh create mode 100644 cpp/src/distance/detail/distance_ops/canberra.cuh create mode 100644 cpp/src/distance/detail/distance_ops/correlation.cuh create mode 100644 cpp/src/distance/detail/distance_ops/cosine.cuh create mode 100644 cpp/src/distance/detail/distance_ops/cutlass.cuh create mode 100644 cpp/src/distance/detail/distance_ops/hamming.cuh create mode 100644 cpp/src/distance/detail/distance_ops/hellinger.cuh create mode 100644 cpp/src/distance/detail/distance_ops/jensen_shannon.cuh create mode 100644 cpp/src/distance/detail/distance_ops/kl_divergence.cuh create mode 100644 cpp/src/distance/detail/distance_ops/l1.cuh create mode 100644 cpp/src/distance/detail/distance_ops/l2_exp.cuh create mode 100644 cpp/src/distance/detail/distance_ops/l2_unexp.cuh create mode 100644 cpp/src/distance/detail/distance_ops/l_inf.cuh create mode 100644 cpp/src/distance/detail/distance_ops/lp_unexp.cuh create mode 100644 cpp/src/distance/detail/distance_ops/russel_rao.cuh create mode 100644 cpp/src/distance/detail/distance_ops/template.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn/custom_epilogue_with_broadcast.h create mode 100644 cpp/src/distance/detail/fused_distance_nn/cutlass_base.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn/epilogue.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn/epilogue_elementwise.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn/fused_cosine_nn.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn/fused_l2_nn.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn/gemm.h create mode 100644 cpp/src/distance/detail/fused_distance_nn/helper_structs.cuh create mode 100644 cpp/src/distance/detail/fused_distance_nn/persistent_gemm.h create mode 100644 cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_normvec_smem.h create mode 100644 cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_reduced_vec.h create mode 100644 cpp/src/distance/detail/fused_distance_nn/simt_kernel.cuh create mode 100644 cpp/src/distance/detail/fused_l2_nn.cuh create mode 100644 cpp/src/distance/detail/kernels/gram_matrix.cuh create mode 100644 cpp/src/distance/detail/kernels/kernel_factory.cuh create mode 100644 cpp/src/distance/detail/kernels/kernel_matrices.cuh create mode 100644 cpp/src/distance/detail/kernels/rbf_fin_op.cuh create mode 100644 cpp/src/distance/detail/masked_distance_base.cuh create mode 100644 cpp/src/distance/detail/masked_nn.cuh create mode 100644 cpp/src/distance/detail/pairwise_distance_base.cuh create mode 100644 cpp/src/distance/detail/pairwise_distance_cutlass_base.cuh create mode 100644 cpp/src/distance/detail/pairwise_distance_epilogue.h create mode 100644 cpp/src/distance/detail/pairwise_distance_epilogue_elementwise.h create mode 100644 cpp/src/distance/detail/pairwise_distance_gemm.h create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch-ext.cuh create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch-inl.cuh create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch.cuh create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_00_generate.py create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l1_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l1_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_layout.cuh create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_rbf.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_double_double_double_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_float_float_float_int.cu create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_sm60.cuh create mode 100644 cpp/src/distance/detail/pairwise_matrix/dispatch_sm80.cuh create mode 100644 cpp/src/distance/detail/pairwise_matrix/kernel_sm60.cuh create mode 100644 cpp/src/distance/detail/pairwise_matrix/params.cuh create mode 100644 cpp/src/distance/detail/predicated_tile_iterator_normvec.h create mode 100644 cpp/src/distance/distance-ext.cuh create mode 100644 cpp/src/distance/distance-inl.cuh create mode 100644 cpp/src/distance/distance.cu create mode 100644 cpp/src/distance/distance.cuh create mode 100644 cpp/src/distance/pairwise_distance.cu create mode 100644 cpp/test/distance/dist_canberra.cu create mode 100644 cpp/test/distance/dist_correlation.cu create mode 100644 cpp/test/distance/dist_cos.cu create mode 100644 cpp/test/distance/dist_hamming.cu create mode 100644 cpp/test/distance/dist_hellinger.cu create mode 100644 cpp/test/distance/dist_inner_product.cu create mode 100644 cpp/test/distance/dist_jensen_shannon.cu create mode 100644 cpp/test/distance/dist_kl_divergence.cu create mode 100644 cpp/test/distance/dist_l1.cu create mode 100644 cpp/test/distance/dist_l2_exp.cu create mode 100644 cpp/test/distance/dist_l2_sqrt_exp.cu create mode 100644 cpp/test/distance/dist_l2_unexp.cu create mode 100644 cpp/test/distance/dist_l_inf.cu create mode 100644 cpp/test/distance/dist_lp_unexp.cu create mode 100644 cpp/test/distance/dist_russell_rao.cu create mode 100644 cpp/test/distance/distance_base.cuh diff --git a/conda/environments/all_cuda-122_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml index 7b50ceb0a0..3c53191605 100644 --- a/conda/environments/all_cuda-122_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -32,6 +32,7 @@ dependencies: - libcurand-dev - libcusolver-dev - libcusparse-dev +- libnvjitlink - make - nccl>=2.9.9 - ninja diff --git a/conda/environments/all_cuda-122_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml index 6c933e193d..4e84731c72 100644 --- a/conda/environments/all_cuda-122_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -32,6 +32,7 @@ dependencies: - libcurand-dev - libcusolver-dev - libcusparse-dev +- libnvjitlink - make - nccl>=2.9.9 - ninja diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index eb9de7a9db..8556e2941e 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -188,6 +188,35 @@ include(cmake/thirdparty/get_cutlass.cmake) add_library( cuvs SHARED + src/distance/detail/pairwise_matrix/dispatch_canberra_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_canberra_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_correlation_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_correlation_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_cosine_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_cosine_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_kl_divergence_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_kl_divergence_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_l1_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_l1_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_l2_expanded_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_l2_expanded_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_l_inf_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_l_inf_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_float_float_float_int.cu + src/distance/detail/pairwise_matrix/dispatch_rbf.cu + src/distance/detail/pairwise_matrix/dispatch_russel_rao_double_double_double_int.cu + src/distance/detail/pairwise_matrix/dispatch_russel_rao_float_float_float_int.cu + src/distance/distance.cu + src/distance/pairwise_distance.cu src/neighbors/brute_force_index.cu src/neighbors/brute_force.cu src/neighbors/cagra_build_float.cpp @@ -240,16 +269,13 @@ target_include_directories( if(NOT BUILD_CPU_ONLY) # Keep cuVS as lightweight as possible. Only CUDA libs and rmm should be used in global target. - target_link_libraries(cuvs - PUBLIC - rmm::rmm - $<$>:raft::raft> - $<$>:raft::compiled> - PRIVATE - $<$:raft::raft> - $<$:raft::compiled_static> - nvidia::cutlass::cutlass - ) + target_link_libraries( + cuvs + PUBLIC rmm::rmm $<$>:raft::raft> + $<$>:raft::compiled> + PRIVATE $<$:raft::raft> + $<$:raft::compiled_static> nvidia::cutlass::cutlass + ) endif() # Endian detection @@ -304,14 +330,14 @@ endif() set_target_properties( cuvs - PROPERTIES BUILD_RPATH "\$ORIGIN" - INSTALL_RPATH "\$ORIGIN" - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON - CUDA_STANDARD 17 - CUDA_STANDARD_REQUIRED ON + PROPERTIES BUILD_RPATH "\$ORIGIN" + INSTALL_RPATH "\$ORIGIN" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CUDA_STANDARD 17 + CUDA_STANDARD_REQUIRED ON INTERFACE_POSITION_INDEPENDENT_CODE ON - POSITION_INDEPENDENT_CODE ON + POSITION_INDEPENDENT_CODE ON ) target_compile_options( @@ -325,25 +351,21 @@ target_link_options(cuvs PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld") # * cuvs_c ------------------------------------------------------------------------------- if(BUILD_C_LIBRARY) add_library( - cuvs_c SHARED - src/core/c_api.cpp - src/neighbors/brute_force_c.cpp - src/neighbors/ivf_flat_c.cpp - src/neighbors/ivf_pq_c.cpp - src/neighbors/cagra_c.cpp + cuvs_c SHARED src/core/c_api.cpp src/neighbors/brute_force_c.cpp src/neighbors/ivf_flat_c.cpp + src/neighbors/ivf_pq_c.cpp src/neighbors/cagra_c.cpp ) add_library(cuvs::c_api ALIAS cuvs_c) set_target_properties( cuvs_c - PROPERTIES BUILD_RPATH "\$ORIGIN" - INSTALL_RPATH "\$ORIGIN" - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON + PROPERTIES BUILD_RPATH "\$ORIGIN" + INSTALL_RPATH "\$ORIGIN" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + POSITION_INDEPENDENT_CODE ON INTERFACE_POSITION_INDEPENDENT_CODE ON - EXPORT_NAME c_api + EXPORT_NAME c_api ) target_compile_options(cuvs_c PRIVATE "$<$:${CUVS_CXX_FLAGS}>") @@ -354,12 +376,11 @@ if(BUILD_C_LIBRARY) INTERFACE "$" ) - target_link_libraries(cuvs_c - PUBLIC - cuvs::cuvs - PRIVATE - $<$:raft::raft> - ) + target_link_libraries( + cuvs_c + PUBLIC cuvs::cuvs + PRIVATE $<$:raft::raft> + ) # ensure CUDA symbols aren't relocated to the middle of the debug build binaries target_link_options(cuvs_c PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld") diff --git a/cpp/include/cuvs/distance/distance_types.h b/cpp/include/cuvs/distance/distance_types.h index 6cc2a993b6..550221e8e6 100644 --- a/cpp/include/cuvs/distance/distance_types.h +++ b/cpp/include/cuvs/distance/distance_types.h @@ -20,7 +20,7 @@ extern "C" { #endif /** enum to tell how to compute distance */ -enum DistanceType { +typedef enum { /** evaluate as dist_ij = sum(x_ik^2) + sum(y_ij)^2 - 2*sum(x_ik * y_jk) */ L2Expanded = 0, @@ -64,7 +64,7 @@ enum DistanceType { DiceExpanded = 19, /** Precomputed (special value) **/ Precomputed = 100 -}; +} cuvsDistanceType; #ifdef __cplusplus } diff --git a/cpp/include/cuvs/distance/distance_types.hpp b/cpp/include/cuvs/distance/distance_types.hpp index 0b2fa4c263..7b18646471 100644 --- a/cpp/include/cuvs/distance/distance_types.hpp +++ b/cpp/include/cuvs/distance/distance_types.hpp @@ -14,56 +14,12 @@ * limitations under the License. */ +#include "distance_types.h" #pragma once namespace cuvs::distance { -/** enum to tell how to compute distance */ -enum DistanceType : unsigned short { - - /** evaluate as dist_ij = sum(x_ik^2) + sum(y_ij)^2 - 2*sum(x_ik * y_jk) */ - L2Expanded = 0, - /** same as above, but inside the epilogue, perform square root operation */ - L2SqrtExpanded = 1, - /** cosine distance */ - CosineExpanded = 2, - /** L1 distance */ - L1 = 3, - /** evaluate as dist_ij += (x_ik - y-jk)^2 */ - L2Unexpanded = 4, - /** same as above, but inside the epilogue, perform square root operation */ - L2SqrtUnexpanded = 5, - /** basic inner product **/ - InnerProduct = 6, - /** Chebyshev (Linf) distance **/ - Linf = 7, - /** Canberra distance **/ - Canberra = 8, - /** Generalized Minkowski distance **/ - LpUnexpanded = 9, - /** Correlation distance **/ - CorrelationExpanded = 10, - /** Jaccard distance **/ - JaccardExpanded = 11, - /** Hellinger distance **/ - HellingerExpanded = 12, - /** Haversine distance **/ - Haversine = 13, - /** Bray-Curtis distance **/ - BrayCurtis = 14, - /** Jensen-Shannon distance**/ - JensenShannon = 15, - /** Hamming distance **/ - HammingUnexpanded = 16, - /** KLDivergence **/ - KLDivergence = 17, - /** RusselRao **/ - RusselRaoExpanded = 18, - /** Dice-Sorensen distance **/ - DiceExpanded = 19, - /** Precomputed (special value) **/ - Precomputed = 100 -}; +using DistanceType = cuvsDistanceType; /** * Whether minimal distance corresponds to similar elements (using the given metric). diff --git a/cpp/include/cuvs/distance/pairwise_distance.hpp b/cpp/include/cuvs/distance/pairwise_distance.hpp new file mode 100644 index 0000000000..27d9af6c15 --- /dev/null +++ b/cpp/include/cuvs/distance/pairwise_distance.hpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +namespace cuvs::distance { + +/** + * @defgroup pairwise_distance Pairwise Distances API + * @{ + */ + +/** + * @brief Compute pairwise distances for two matrices + * + * Note: Only contiguous row- or column-major layouts supported currently. + * + * Usage example: + * @code{.cpp} + * #include + * #include + * #include + * + * raft::raft::resources handle; + * int n_samples = 5000; + * int n_features = 50; + * + * auto input = raft::make_device_matrix(handle, n_samples, n_features); + * + * // ... fill input with data ... + * + * auto output = raft::make_device_matrix(handle, n_samples, n_samples); + * + * auto metric = cuvs::distance::DistanceType::L2SqrtExpanded; + * cuvs::distance::pairwise_distance(handle, + * raft::make_const(input.view()), + * raft::make_const(input.view()), + * output.view(), + * metric); + * @endcode + * + * @param[in] handle raft handle for managing expensive resources + * @param[in] x first set of points (size n*k) + * @param[in] y second set of points (size m*k) + * @param[out] dist output distance matrix (size n*m) + * @param[in] metric distance to evaluate + * @param[in] metric_arg metric argument (used for Minkowski distance) + */ +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + float metric_arg = 2.0f); + +/** + * @brief Compute pairwise distances for two matrices + * + * Note: Only contiguous row- or column-major layouts supported currently. + * + * Usage example: + * @code{.cpp} + * #include + * #include + * #include + * + * raft::raft::resources handle; + * int n_samples = 5000; + * int n_features = 50; + * + * auto input = raft::make_device_matrix(handle, n_samples, n_features); + * + * // ... fill input with data ... + * + * auto output = raft::make_device_matrix(handle, n_samples, n_samples); + * + * auto metric = cuvs::distance::DistanceType::L2SqrtExpanded; + * cuvs::distance::pairwise_distance(handle, + * raft::make_const(input.view()), + * raft::make_const(input.view()), + * output.view(), + * metric); + * @endcode + * + * @param[in] handle raft handle for managing expensive resources + * @param[in] x first set of points (size n*k) + * @param[in] y second set of points (size m*k) + * @param[out] dist output distance matrix (size n*m) + * @param[in] metric distance to evaluate + * @param[in] metric_arg metric argument (used for Minkowski distance) + */ +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + double metric_arg = 2.0f); + +/** + * @brief Compute pairwise distances for two matrices + * + * Note: Only contiguous row- or column-major layouts supported currently. + * + * Usage example: + * @code{.cpp} + * #include + * #include + * #include + * + * raft::raft::resources handle; + * int n_samples = 5000; + * int n_features = 50; + * + * auto input = raft::make_device_matrix(handle, n_samples, n_features); + * + * // ... fill input with data ... + * + * auto output = raft::make_device_matrix(handle, n_samples, n_samples); + * + * auto metric = cuvs::distance::DistanceType::L2SqrtExpanded; + * cuvs::distance::pairwise_distance(handle, + * raft::make_const(input.view()), + * raft::make_const(input.view()), + * output.view(), + * metric); + * @endcode + * + * @param[in] handle raft handle for managing expensive resources + * @param[in] x first set of points (size n*k) + * @param[in] y second set of points (size m*k) + * @param[out] dist output distance matrix (size n*m) + * @param[in] metric distance to evaluate + * @param[in] metric_arg metric argument (used for Minkowski distance) + */ +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + float metric_arg = 2.0f); +/** + * @brief Compute pairwise distances for two matrices + * + * Note: Only contiguous row- or column-major layouts supported currently. + * + * Usage example: + * @code{.cpp} + * #include + * #include + * #include + * + * raft::raft::resources handle; + * int n_samples = 5000; + * int n_features = 50; + * + * auto input = raft::make_device_matrix(handle, n_samples, n_features); + * + * // ... fill input with data ... + * + * auto output = raft::make_device_matrix(handle, n_samples, n_samples); + * + * auto metric = cuvs::distance::DistanceType::L2SqrtExpanded; + * cuvs::distance::pairwise_distance(handle, + * raft::make_const(input.view()), + * raft::make_const(input.view()), + * output.view(), + * metric); + * @endcode + * + * @param[in] handle raft handle for managing expensive resources + * @param[in] x first set of points (size n*k) + * @param[in] y second set of points (size m*k) + * @param[out] dist output distance matrix (size n*m) + * @param[in] metric distance to evaluate + * @param[in] metric_arg metric argument (used for Minkowski distance) + */ +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + double metric_arg = 2.0f); + +/** @} */ // end group pairwise_distance_runtime + +} // namespace cuvs::distance diff --git a/cpp/include/cuvs/neighbors/brute_force.h b/cpp/include/cuvs/neighbors/brute_force.h index cb7ee2a30e..145bb55550 100644 --- a/cpp/include/cuvs/neighbors/brute_force.h +++ b/cpp/include/cuvs/neighbors/brute_force.h @@ -102,7 +102,7 @@ cuvsError_t cuvsBruteForceIndexDestroy(cuvsBruteForceIndex_t index); */ cuvsError_t cuvsBruteForceBuild(cuvsResources_t res, DLManagedTensor* dataset, - enum DistanceType metric, + cuvsDistanceType metric, float metric_arg, cuvsBruteForceIndex_t index); /** diff --git a/cpp/include/cuvs/neighbors/ivf_flat.h b/cpp/include/cuvs/neighbors/ivf_flat.h index 08200ae7da..22c4d361ca 100644 --- a/cpp/include/cuvs/neighbors/ivf_flat.h +++ b/cpp/include/cuvs/neighbors/ivf_flat.h @@ -36,7 +36,7 @@ extern "C" { */ struct ivfFlatIndexParams { /** Distance type. */ - enum DistanceType metric; + cuvsDistanceType metric; /** The argument used by some distance metrics. */ float metric_arg; /** diff --git a/cpp/include/cuvs/neighbors/ivf_pq.h b/cpp/include/cuvs/neighbors/ivf_pq.h index c1fcaed86a..2a8269eb07 100644 --- a/cpp/include/cuvs/neighbors/ivf_pq.h +++ b/cpp/include/cuvs/neighbors/ivf_pq.h @@ -45,7 +45,7 @@ enum codebook_gen { // NOLINT */ struct ivfPqIndexParams { /** Distance type. */ - enum DistanceType metric; + cuvsDistanceType metric; /** The argument used by some distance metrics. */ float metric_arg; /** diff --git a/cpp/src/distance/detail/compress_to_bits.cuh b/cpp/src/distance/detail/compress_to_bits.cuh new file mode 100644 index 0000000000..9ce47774a6 --- /dev/null +++ b/cpp/src/distance/detail/compress_to_bits.cuh @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +namespace cuvs::distance::detail { + +/** + * @brief Compress 2D boolean matrix to bitfield + * + * Utility kernel for masked_l2_nn. + * + * @tparam T + * + * @parameter[in] in An `m x n` boolean matrix. Row major. + * @parameter[out] out An `(m / bits_per_elem) x n` matrix with elements of + * type T, where T is of size `bits_per_elem` bits. + * Note: the division (`/`) is a ceilDiv. + */ +template ::value>> +RAFT_KERNEL compress_to_bits_kernel( + raft::device_matrix_view in, + raft::device_matrix_view out) +{ + constexpr int bits_per_element = 8 * sizeof(T); + constexpr int tile_dim_m = bits_per_element; + constexpr int nthreads = 128; + constexpr int tile_dim_n = nthreads; // read 128 bools at once = 1 sector + + // Tile in shared memory is transposed + __shared__ bool smem[tile_dim_n][tile_dim_m]; + + const int num_tiles_per_m = raft::ceildiv(in.extent(0), tile_dim_m); + const int num_tiles_per_n = raft::ceildiv(in.extent(1), tile_dim_n); + + for (int lin_tile_idx = blockIdx.x; true; lin_tile_idx += gridDim.x) { + const int tile_idx_n = tile_dim_n * (lin_tile_idx % num_tiles_per_n); + const int tile_idx_m = tile_dim_m * (lin_tile_idx / num_tiles_per_n); + + if (in.extent(0) <= tile_idx_m) { break; } + // Fill shared memory tile + bool reg_buf[tile_dim_m]; +#pragma unroll + for (int i = 0; i < tile_dim_m; ++i) { + const int in_m = tile_idx_m + i; + const int in_n = tile_idx_n + threadIdx.x; + bool in_bounds = in_m < in.extent(0) && in_n < in.extent(1); + reg_buf[i] = in_bounds ? in(in_m, in_n) : false; + smem[threadIdx.x][i] = reg_buf[i]; + } + __syncthreads(); + + // Drain memory tile into single output element out_elem. + T out_elem{0}; +#pragma unroll + for (int j = 0; j < tile_dim_n; ++j) { + if (smem[threadIdx.x][j]) { out_elem |= T(1) << j; } + } + __syncthreads(); + + // Write output. + int out_m = tile_idx_m / bits_per_element; + int out_n = tile_idx_n + threadIdx.x; + + if (out_m < out.extent(0) && out_n < out.extent(1)) { out(out_m, out_n) = out_elem; } + } +} + +/** + * @brief Compress 2D boolean matrix to bitfield + * + * Utility kernel for masked_l2_nn. + * + * @tparam T + * + * @parameter[in] in An `m x n` boolean matrix. Row major. + * @parameter[out] out An `(m / bits_per_elem) x n` matrix with elements of + * type T, where T is of size `bits_per_elem` bits. + * Note: the division (`/`) is a ceilDiv. + */ +template ::value>> +void compress_to_bits(raft::resources const& handle, + raft::device_matrix_view in, + raft::device_matrix_view out) +{ + auto stream = resource::get_cuda_stream(handle); + constexpr int bits_per_element = 8 * sizeof(T); + + RAFT_EXPECTS(raft::ceildiv(in.extent(0), bits_per_element) == out.extent(0), + "Number of output rows must be ceildiv(input rows, bits_per_elem)"); + RAFT_EXPECTS(in.extent(1) == out.extent(1), "Number of output columns must equal input columns."); + + const int num_SMs = raft::getMultiProcessorCount(); + int blocks_per_sm = 0; + constexpr int num_threads = 128; + constexpr int dyn_smem_size = 0; + RAFT_CUDA_TRY(cudaOccupancyMaxActiveBlocksPerMultiprocessor( + &blocks_per_sm, compress_to_bits_kernel, num_threads, dyn_smem_size)); + + dim3 grid(num_SMs * blocks_per_sm); + dim3 block(128); + compress_to_bits_kernel<<>>(in, out); + RAFT_CUDA_TRY(cudaGetLastError()); +} + +}; // namespace cuvs::distance::detail diff --git a/cpp/src/distance/detail/distance.cuh b/cpp/src/distance/detail/distance.cuh new file mode 100644 index 0000000000..7765bc672a --- /dev/null +++ b/cpp/src/distance/detail/distance.cuh @@ -0,0 +1,815 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "distance_ops/all_ops.cuh" +#include "pairwise_matrix/dispatch.cuh" +#include "pairwise_matrix/dispatch_sm60.cuh" +#include "pairwise_matrix/dispatch_sm80.cuh" +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace cuvs { +namespace distance { +namespace detail { + +/** + * @brief: A tag type for overload resolution based on DistanceType + * + * It is not possible to partially specialize function templates on a single + * parameter. Instead, it is often easier to use a combination of conventional + * method overloading and a parameter with a specific tag type. The following + * type is used to help method overloading based on the DistanceType enum. + */ +template +using distance_tag = std::integral_constant; + +/** + * @brief Implement pairwise_matrix for specific distance + * + * There are multiple overloads for this function, one for each distance type. + * They are implemented below. The documentation of this function serves as + * documentation for all functions. The following overloads are defined: + * + * - DistanceType::Canberra: + * - DistanceType::CorrelationExpanded: + * - DistanceType::CosineExpanded: + * - DistanceType::HammingUnexpanded: + * - DistanceType::HellingerExpanded: + * - DistanceType::JensenShannon: + * - DistanceType::KLDivergence: + * - DistanceType::L1: + * - DistanceType::L2Expanded: + * - DistanceType::L2SqrtExpanded: + * - DistanceType::L2Unexpanded: + * - DistanceType::L2SqrtUnexpanded: + * - DistanceType::Linf: + * - DistanceType::LpUnexpanded: + * - DistanceType::RusselRaoExpanded: + * + * @tparam DataT Input data type + * @tparam AccT Accumulation data type + * @tparam OutT Output data type + * @tparam FinOpT Type of final operation + * @tparam IdxT Index type + * + * @param handle RAFT resources handle + * @param distance_type A tag type to indicate which distance is calculated. + * @param x First set of points + * @param y Second set of points + * @param out Output distance matrix + * @param m Number of points in x + * @param n Number of points in y + * @param k Dimensionality of points in x, y + * @param workspace Temporary workspace needed for computations + * @param worksize Number of bytes of the workspace + * @param is_row_major Whether the matrices are row-major or col-major + * @param metric_arg The `p` argument for Lp. + */ +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT* workspace, // unused + size_t worksize, // unused + FinOpT fin_op, + bool is_row_major, + DataT metric_arg) // unused +{ + ops::canberra_distance_op distance_op{}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT* workspace, + size_t worksize, + FinOpT fin_op, + bool is_row_major, + DataT) // unused +{ + ASSERT(!(worksize < 2 * (m + n) * sizeof(AccT)), "workspace size error"); + ASSERT(workspace != nullptr, "workspace is null"); + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + AccT* x_norm = workspace; + AccT* y_norm = workspace; + AccT* sq_x_norm = workspace; + AccT* sq_y_norm = workspace; + // TODO: Column major case looks to have lower accuracy for X == Y, + // perhaps the use of stridedSummationKernel could be causing this, + // need to investigate and fix. + if (x == y && is_row_major) { + raft::linalg::reduce(x_norm, + x, + k, + std::max(m, n), + (AccT)0, + is_row_major, + true, + stream, + false, + raft::identity_op(), + raft::add_op()); + sq_x_norm += std::max(m, n); + sq_y_norm = sq_x_norm; + raft::linalg::rowNorm( + sq_x_norm, x, k, std::max(m, n), raft::linalg::L2Norm, is_row_major, stream); + } else { + y_norm += m; + raft::linalg::reduce(x_norm, + x, + k, + m, + (AccT)0, + is_row_major, + true, + stream, + false, + raft::identity_op(), + raft::add_op()); + raft::linalg::reduce(y_norm, + y, + k, + n, + (AccT)0, + is_row_major, + true, + stream, + false, + raft::identity_op(), + raft::add_op()); + + sq_x_norm += (m + n); + sq_y_norm = sq_x_norm + m; + raft::linalg::rowNorm(sq_x_norm, x, k, m, raft::linalg::L2Norm, is_row_major, stream); + raft::linalg::rowNorm(sq_y_norm, y, k, n, raft::linalg::L2Norm, is_row_major, stream); + } + + using OpT = ops::correlation_distance_op; + OpT corr_op(is_row_major, sq_x_norm, sq_y_norm, m, n, k); + pairwise_matrix_dispatch( + corr_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT* workspace, + size_t worksize, + FinOpT fin_op, + bool is_row_major, + DataT) // unused +{ + // raft distance support inputs as float/double and output as uint8_t/float/double. + static_assert(!((sizeof(OutT) > 1) && (sizeof(AccT) != sizeof(OutT))), + "OutT can be uint8_t, float, double," + "if sizeof(OutT) > 1 then sizeof(AccT) == sizeof(OutT)."); + + ASSERT(!(worksize < (m + n) * sizeof(AccT)), "workspace size error"); + ASSERT(workspace != nullptr, "workspace is null"); + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + DataT* x_norm = workspace; + DataT* y_norm = workspace; + // TODO: Column major case looks to have lower accuracy for X == Y, + // perhaps the use of stridedSummationKernel could be causing this, + // need to investigate and fix. + if (x == y && is_row_major) { + raft::linalg::rowNorm( + x_norm, x, k, std::max(m, n), raft::linalg::L2Norm, is_row_major, stream, raft::sqrt_op{}); + } else { + y_norm += m; + raft::linalg::rowNorm( + x_norm, x, k, m, raft::linalg::L2Norm, is_row_major, stream, raft::sqrt_op{}); + raft::linalg::rowNorm( + y_norm, y, k, n, raft::linalg::L2Norm, is_row_major, stream, raft::sqrt_op{}); + } + + ops::cosine_distance_op distance_op{}; + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + ops::hamming_distance_op distance_op{k}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + raft::linalg::gemm(handle, + out, + const_cast(x), + const_cast(y), + m, + n, + k, + !is_row_major, + !is_row_major, + is_row_major, + stream); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + // First sqrt x and y + const auto raft_sqrt = raft::linalg::unaryOp; + + raft_sqrt((DataT*)x, x, m * k, raft::sqrt_op{}, stream); + if (x != y) { raft_sqrt((DataT*)y, y, n * k, raft::sqrt_op{}, stream); } + + // Then calculate Hellinger distance + ops::hellinger_distance_op distance_op{}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); + + // Finally revert sqrt of x and y + raft_sqrt((DataT*)x, x, m * k, raft::sqrt_op{}, stream); + if (x != y) { raft_sqrt((DataT*)y, y, n * k, raft::sqrt_op{}, stream); } + + RAFT_CUDA_TRY(cudaGetLastError()); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + ops::jensen_shannon_distance_op distance_op{}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + auto unaryOp_lambda = [] __device__(DataT input) { + const bool x_zero = (input == 0); + return (!x_zero) * raft::log(input + x_zero); + }; + + auto unaryOp_lambda_reverse = [] __device__(DataT input) { + // reverse previous log (x) back to x using (e ^ log(x)) + const bool x_zero = (input == 0); + return (!x_zero) * raft::exp(input); + }; + + if (x != y) { + raft::linalg::unaryOp( + (DataT*)y, y, n * k, unaryOp_lambda, stream); + } + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + // This op takes some shortcuts when x equals y. So its behavior changes based + // on this. + ops::kl_divergence_op distance_op{is_row_major, x == y}; + + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); + + if (x != y) { + // Now reverse previous log (x) back to x using (e ^ log(x)) + raft::linalg::unaryOp( + (DataT*)y, y, n * k, unaryOp_lambda_reverse, stream); + } +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + ops::l1_distance_op distance_op{}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl_l2_expanded( // NOTE: different name + bool perform_sqrt, // dispatch on sqrt + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT* workspace, + size_t worksize, + FinOpT fin_op, + cudaStream_t stream, + bool is_row_major) +{ + // raft distance support inputs as float/double and output as uint8_t/float/double. + static_assert(!((sizeof(OutT) > 1) && (sizeof(AccT) != sizeof(OutT))), + "OutT can be uint8_t, float, double," + "if sizeof(OutT) > 1 then sizeof(AccT) == sizeof(OutT)."); + + ASSERT(!(worksize < (m + n) * sizeof(AccT)), "workspace size error"); + ASSERT(workspace != nullptr, "workspace is null"); + + DataT* x_norm = workspace; + DataT* y_norm = workspace; + // TODO: Column major case looks to have lower accuracy for X == Y, + // perhaps the use of stridedSummationKernel could be causing this, + // need to investigate and fix. + if ((x == y) && is_row_major) { + raft::linalg::rowNorm(x_norm, + x, + k, + std::max(m, n), + raft::linalg::L2Norm, + is_row_major, + stream, + raft::identity_op{}); + } else { + y_norm += m; + raft::linalg::rowNorm( + x_norm, x, k, m, raft::linalg::L2Norm, is_row_major, stream, raft::identity_op{}); + raft::linalg::rowNorm( + y_norm, y, k, n, raft::linalg::L2Norm, is_row_major, stream, raft::identity_op{}); + } + + ops::l2_exp_distance_op distance_op{perform_sqrt}; + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT* workspace, + size_t worksize, + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + bool perform_sqrt = false; + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + distance_impl_l2_expanded( + perform_sqrt, x, y, out, m, n, k, workspace, worksize, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT* workspace, + size_t worksize, + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + bool perform_sqrt = true; + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + distance_impl_l2_expanded( + perform_sqrt, x, y, out, m, n, k, workspace, worksize, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + bool perform_sqrt = false; + ops::l2_unexp_distance_op l2_op(perform_sqrt); + + // The unexpanded L2 does not require the norms of a and b to be calculated. + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + pairwise_matrix_dispatch( + l2_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + bool perform_sqrt = true; + ops::l2_unexp_distance_op l2_op(perform_sqrt); + + // The unexpanded L2 does not require the norms of a and b to be calculated. + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + pairwise_matrix_dispatch( + l2_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + ops::l_inf_distance_op distance_op{}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT metric_arg) +{ + ops::lp_unexp_distance_op distance_op{metric_arg}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +template +void distance_impl(raft::resources const& handle, + distance_tag distance_type, + const DataT* x, + const DataT* y, + OutT* out, + IdxT m, + IdxT n, + IdxT k, + AccT*, // workspace unused + size_t, // worksize unused + FinOpT fin_op, + bool is_row_major, + DataT) // metric_arg unused +{ + ops::russel_rao_distance_op distance_op{k}; + + const DataT* x_norm = nullptr; + const DataT* y_norm = nullptr; + + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + pairwise_matrix_dispatch( + distance_op, m, n, k, x, y, x_norm, y_norm, out, fin_op, stream, is_row_major); +} + +/** + * @brief Evaluate pairwise distances with the user epilogue lamba allowed + * @tparam DistanceType which distance to evaluate + * @tparam InType input argument type + * @tparam AccType accumulation type + * @tparam OutType output type + * @tparam FinalLambda user-defined epilogue lamba + * @tparam Index_ Index type + * + * @param x first set of points + * @param y second set of points + * @param out output distance matrix + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * @param workspace temporary workspace needed for computations + * @param worksize number of bytes of the workspace + * @param fin_op the final gemm epilogue lambda + * @param stream cuda stream + * @param isRowMajor whether the matrices are row-major or col-major + * + * @note fin_op: This is a device lambda which is supposed to operate upon the + * input which is AccType and returns the output in OutType. It's signature is + * as follows:
OutType fin_op(AccType in, int g_idx);
. If one needs + * any other parameters, feel free to pass them via closure. + */ +template +void distance(raft::resources const& handle, + const InType* x, + const InType* y, + OutType* out, + Index_ m, + Index_ n, + Index_ k, + void* workspace, + size_t worksize, + FinalLambda fin_op, + bool isRowMajor = true, + InType metric_arg = 2.0f) +{ + // raft distance support inputs as float/double and output as uint8_t/float/double. + static_assert(!((sizeof(OutType) > 1) && (sizeof(AccType) != sizeof(OutType))), + "OutType can be uint8_t, float, double," + "if sizeof(OutType) > 1 then sizeof(AccType) == sizeof(OutType)."); + + distance_impl( + handle, + distance_tag{}, + x, + y, + out, + m, + n, + k, + reinterpret_cast(workspace), + worksize, + fin_op, + isRowMajor, + metric_arg); + RAFT_CUDA_TRY(cudaPeekAtLastError()); +} + +/** + * @brief Evaluate pairwise distances for the simple use case + * @tparam DistanceType which distance to evaluate + * @tparam InType input argument type + * @tparam AccType accumulation type + * @tparam OutType output type + * @tparam Index_ Index type + * @param x first set of points + * @param y second set of points + * @param dist output distance matrix + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * @param workspace temporary workspace needed for computations + * @param worksize number of bytes of the workspace + * @param stream cuda stream + * @param isRowMajor whether the matrices are row-major or col-major + */ +template +void distance(raft::resources const& handle, + const InType* x, + const InType* y, + OutType* out, + Index_ m, + Index_ n, + Index_ k, + void* workspace, + size_t worksize, + bool isRowMajor = true, + InType metric_arg = 2.0f) +{ + auto fin_op = raft::identity_op(); + + distance( + handle, x, y, out, m, n, k, workspace, worksize, fin_op, isRowMajor, metric_arg); +} + +/** + * @brief Return the exact workspace size to compute the distance + * @tparam DistanceType which distance to evaluate + * @tparam InType input argument type + * @tparam AccType accumulation type + * @tparam OutType output type + * @tparam Index_ Index type + * @param x first set of points + * @param y second set of points + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * + * @note If the specified distanceType doesn't need the workspace at all, it + * returns 0. + */ +template +size_t getWorkspaceSize(const InType* x, const InType* y, Index_ m, Index_ n, Index_ k) +{ + size_t worksize = 0; + constexpr bool is_allocated = (distanceType <= cuvs::distance::DistanceType::CosineExpanded) || + (distanceType == cuvs::distance::DistanceType::CorrelationExpanded); + constexpr int numOfBuffers = + (distanceType == cuvs::distance::DistanceType::CorrelationExpanded) ? 2 : 1; + + if (is_allocated) { + // TODO : when X == Y allocate std::max(m, n) instead of m + n when column major input + // accuracy issue is resolved until then we allocate as m + n. + worksize += numOfBuffers * m * sizeof(AccType); + worksize += numOfBuffers * n * sizeof(AccType); + } + + return worksize; +} + +}; // namespace detail +}; // namespace distance +}; // namespace cuvs diff --git a/cpp/src/distance/detail/distance_ops/all_ops.cuh b/cpp/src/distance/detail/distance_ops/all_ops.cuh new file mode 100644 index 0000000000..55989697f7 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/all_ops.cuh @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// Defines a named requirement "has_cutlass_op" +#include "../distance_ops/cutlass.cuh" + +// The distance operations: +#include "../distance_ops/canberra.cuh" +#include "../distance_ops/correlation.cuh" +#include "../distance_ops/cosine.cuh" +#include "../distance_ops/hamming.cuh" +#include "../distance_ops/hellinger.cuh" +#include "../distance_ops/jensen_shannon.cuh" +#include "../distance_ops/kl_divergence.cuh" +#include "../distance_ops/l1.cuh" +#include "../distance_ops/l2_exp.cuh" +#include "../distance_ops/l2_unexp.cuh" +#include "../distance_ops/l_inf.cuh" +#include "../distance_ops/lp_unexp.cuh" +#include "../distance_ops/russel_rao.cuh" diff --git a/cpp/src/distance/detail/distance_ops/canberra.cuh b/cpp/src/distance/detail/distance_ops/canberra.cuh new file mode 100644 index 0000000000..8bbdc9945b --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/canberra.cuh @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // raft::abs +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief The canberra distance matrix calculation + * + * It computes the following equation: + * + * c_ij = sum_k |x_ik - y_kj| / ( |x_ik| + |y_kj| ) + */ +template +struct canberra_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = true; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const + { + const auto diff = raft::abs(x - y); + const auto add = raft::abs(x) + raft::abs(y); + // deal with potential for 0 in denominator by + // forcing 0/1 instead + acc += ((add != 0) * diff / (add + (add == 0))); + }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + return; + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/correlation.cuh b/cpp/src/distance/detail/distance_ops/correlation.cuh new file mode 100644 index 0000000000..f033f3dfad --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/correlation.cuh @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // DI + +namespace cuvs::distance::detail::ops { + +/** @brief The correlation distance + * + * It computes the following equation: + * + * d(x, y) = ((x - mean(x)) ⋅ (y - mean(y))) + * / + * (|| x - mean(x) ||_2 || y - mean(y) ||_2) + */ +template +struct correlation_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + const DataT* x2n; + const DataT* y2n; + IdxT m; + IdxT n; + IdxT k; + + correlation_distance_op( + bool is_row_major, const DataT* x2n_, const DataT* y2n_, IdxT m_, IdxT n_, IdxT k_) noexcept + : x2n(x2n_), y2n(y2n_), m(m_), n(n_), k(k_) + { + // The distance op is typically created before the row-major/col-major + // swapping has been done. So we do it here. + if (!is_row_major) { + std::swap(x2n, y2n); + std::swap(m, n); + } + } + + // Load norms of input data + static constexpr bool use_norms = true; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize + (2 * (Policy::Mblk + Policy::Nblk) * sizeof(DataT)); + } + + DI void core(AccT& acc, DataT& x, DataT& y) const { acc += x * y; }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + // Note how we can sneakily get a pointer to shared memory here, to store + // more data. If the implementation of PairwiseDistanceMatKernel ever + // changes, this will be where we find the bugs. + extern __shared__ char smem[]; + + DataT regx2n[Policy::AccRowsPerTh], regy2n[Policy::AccColsPerTh]; + + DataT* sx2Norm = + (DataT*)(&smem[Policy::SmemSize + (Policy::Mblk + Policy::Nblk) * sizeof(DataT)]); + DataT* sy2Norm = (&sx2Norm[Policy::Mblk]); + + // Load x & y norms required by this threadblock in shmem buffer + if (gridStrideX == blockIdx.x * Policy::Nblk) { + for (int i = threadIdx.x; i < Policy::Mblk; i += Policy::Nthreads) { + auto idx = gridStrideY + i; + sx2Norm[i] = idx < m ? x2n[idx] : 0; + } + } + + for (int i = threadIdx.x; i < Policy::Nblk; i += Policy::Nthreads) { + auto idx = gridStrideX + i; + sy2Norm[i] = idx < n ? y2n[idx] : 0; + } + __syncthreads(); + +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { + regx2n[i] = sx2Norm[i * Policy::AccThRows + (threadIdx.x / Policy::AccThCols)]; + } +#pragma unroll + for (int i = 0; i < Policy::AccColsPerTh; ++i) { + regy2n[i] = sy2Norm[i * Policy::AccThCols + (threadIdx.x % Policy::AccThCols)]; + } + +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + auto numer = k * acc[i][j] - (regxn[i] * regyn[j]); + auto Q_denom = k * regx2n[i] - (regxn[i] * regxn[i]); + auto R_denom = k * regy2n[j] - (regyn[j] * regyn[j]); + + acc[i][j] = 1 - (numer / raft::sqrt(Q_denom * R_denom)); + } + } + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/cosine.cuh b/cpp/src/distance/detail/distance_ops/cosine.cuh new file mode 100644 index 0000000000..d487316513 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/cosine.cuh @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // DI + +namespace cuvs::distance::detail::ops { + +// Epilogue operator for CUTLASS based kernel +template +struct cosine_cutlass_op { + __device__ cosine_cutlass_op() noexcept {} + __device__ AccT operator()(DataT& aNorm, const DataT& bNorm, DataT& accVal) const noexcept + { + return static_cast(1.0) - static_cast(accVal / (aNorm * bNorm)); + } + __device__ AccT operator()(DataT aData) const noexcept { return aData; } +}; + +/** + * @brief the expanded cosine distance matrix calculation + * + * It computes the following equation: + * + * d(x, y) = 1 - (x ⋅ y) / ( ||x||_2 ||y||_2) + */ +template +struct cosine_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + // Load norms of input data + static constexpr bool use_norms = true; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize + ((Policy::Mblk + Policy::Nblk) * sizeof(DataT)); + } + + DI void core(AccT& acc, DataT& x, DataT& y) const { acc += x * y; }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] = 1.0 - (acc[i][j] / (regxn[i] * regyn[j])); + } + } + } + + constexpr cosine_cutlass_op get_cutlass_op() const + { + return cosine_cutlass_op(); + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/cutlass.cuh b/cpp/src/distance/detail/distance_ops/cutlass.cuh new file mode 100644 index 0000000000..6d928314d7 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/cutlass.cuh @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // std::false_type +#include // std::declval + +namespace cuvs::distance::detail::ops { + +// This file defines the named requirement "has_cutlass_op" that can be used to +// determine if a distance operation has a CUTLASS op that can be used to pass +// to CUTLASS. Examples of distance operations that satisfy this requirement are +// cosine_distance_op and l2_exp_distance_op. + +// Primary template handles types that do not support CUTLASS. +// This pattern is described in: +// https://en.cppreference.com/w/cpp/types/void_t +template +struct has_cutlass_op : std::false_type {}; + +// Specialization recognizes types that do support CUTLASS +template +struct has_cutlass_op().get_cutlass_op())>> + : std::true_type {}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/hamming.cuh b/cpp/src/distance/detail/distance_ops/hamming.cuh new file mode 100644 index 0000000000..7c6553f387 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/hamming.cuh @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the Hamming Unexpanded distance matrix calculation + * It computes the following equation: + * + * c_ij = sum_k (x_ik != y_kj) / k + */ +template +struct hamming_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + IdxT k; + + hamming_distance_op(IdxT k_) noexcept : k(k_) {} + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const { acc += (x != y); }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + const DataT one_over_k = DataT(1.0) / k; +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] *= one_over_k; + } + } + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/hellinger.cuh b/cpp/src/distance/detail/distance_ops/hellinger.cuh new file mode 100644 index 0000000000..ad5ca31564 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/hellinger.cuh @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the Hellinger distance matrix calculation + * + * It computes the following equation: + * + * c_ij = sqrt(1 - sum_k sqrt(x_ik * y_kj)) + * + */ +template +struct hellinger_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const + { + // This is sqrt(x) * sqrt(y). + const auto product = x * y; + acc += product; + }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + // Adjust to replace NaN in sqrt with 0 if input to sqrt is negative + const auto finalVal = (1 - acc[i][j]); + const auto rectifier = (!signbit(finalVal)); + acc[i][j] = raft::sqrt(rectifier * finalVal); + } + } + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/jensen_shannon.cuh b/cpp/src/distance/detail/distance_ops/jensen_shannon.cuh new file mode 100644 index 0000000000..2166394941 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/jensen_shannon.cuh @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include // raft::log +#include // DI + +namespace cuvs::distance::detail::ops { + +// Describes the computation the jensen_shannon distance + +/** + * @brief the Jensen Shannon distance matrix calculation + * + * It computes the following equation: + * + * c_ij = sqrt(0.5 * sum( -x_i * (log(0.5 * (x_i + y_i)) - log(x_i)) + * + (-y_i * (log(0.5 * (x_i + y_i)) - log(y_i))))) + */ +template +struct jensen_shannon_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = true; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const + { + const DataT m = 0.5f * (x + y); + const bool m_zero = (m == 0); + const auto logM = (!m_zero) * raft::log(m + m_zero); + + const bool x_zero = (x == 0); + const bool y_zero = (y == 0); + acc += (-x * (logM - raft::log(x + x_zero))) + (-y * (logM - raft::log(y + y_zero))); + }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] = raft::sqrt(0.5 * acc[i][j]); + } + } + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/kl_divergence.cuh b/cpp/src/distance/detail/distance_ops/kl_divergence.cuh new file mode 100644 index 0000000000..929c3a5596 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/kl_divergence.cuh @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include // raft::log +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the KL Divergence distance matrix calculation + * + * It computes the following equation: + * + * c_ij = 0.5 * sum(x * log (x / y)); + */ +template +struct kl_divergence_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + const bool is_row_major; + const bool x_equal_y; + + kl_divergence_op(bool row_major_, bool x_equal_y_ = false) noexcept + : is_row_major(row_major_), x_equal_y(x_equal_y_) + { + } + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = true; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const + { + // TODO: make sure that these branches get hoisted out of main loop.. Could + // be quite expensive otherwise. + if (x_equal_y) { + if (is_row_major) { + const bool x_zero = (x == 0); + const bool y_zero = (y == 0); + acc += x * (raft::log(x + x_zero) - (!y_zero) * raft::log(y + y_zero)); + } else { + const bool y_zero = (y == 0); + const bool x_zero = (x == 0); + acc += y * (raft::log(y + y_zero) - (!x_zero) * raft::log(x + x_zero)); + } + } else { + if (is_row_major) { + const bool x_zero = (x == 0); + acc += x * (raft::log(x + x_zero) - y); + } else { + const bool y_zero = (y == 0); + acc += y * (raft::log(y + y_zero) - x); + } + } + }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] = (0.5f * acc[i][j]); + } + } + } +}; +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/l1.cuh b/cpp/src/distance/detail/distance_ops/l1.cuh new file mode 100644 index 0000000000..76eaffaf30 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/l1.cuh @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the L1 distance matrix calculation + * + * It computes the following equation: + * + * c_ij = sum_k abs(x_ik - y_kj) + */ +template +struct l1_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + // Do not load norms of data, the computation of L1 distance does not use them. + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const { acc += raft::abs(x - y); }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + return; + }; +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/l2_exp.cuh b/cpp/src/distance/detail/distance_ops/l2_exp.cuh new file mode 100644 index 0000000000..f45c412069 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/l2_exp.cuh @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * Reserve 1 digit of precision from each floating-point type + * for round-off error tolerance. + * @tparam DataT + */ +template +__device__ constexpr DataT get_clamp_precision() +{ + switch (sizeof(DataT)) { + case 2: return 1e-3; + case 4: return 1e-6; + case 8: return 1e-15; + default: return 0; + } +} + +// Epilogue operator for CUTLASS based kernel +template +struct l2_exp_cutlass_op { + bool sqrt; + + __device__ l2_exp_cutlass_op() noexcept : sqrt(false) {} + __device__ l2_exp_cutlass_op(bool isSqrt) noexcept : sqrt(isSqrt) {} + inline __device__ AccT operator()(DataT aNorm, DataT bNorm, DataT accVal) const noexcept + { + AccT outVal = aNorm + bNorm - DataT(2.0) * accVal; + + /** + * Self-neighboring points should have (aNorm == bNorm) == accVal and the dot product (accVal) + * can sometimes have round-off errors, which will cause (aNorm == bNorm) ~ accVal instead. + */ + outVal = outVal * !((outVal * outVal < get_clamp_precision()) * (aNorm == bNorm)); + return sqrt ? raft::sqrt(outVal * (outVal > 0)) : outVal; + } + + __device__ AccT operator()(DataT aData) const noexcept { return aData; } +}; + +/** + * @brief the expanded euclidean distance matrix calculation + * + * It computes the following equation: + * + * c_ij = - 2 sum_k x_ik * y_kj + ||x_i.||_2 + ||y_.j||_2 + * + */ +template +struct l2_exp_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + const bool sqrt; + + l2_exp_distance_op(bool sqrt_) noexcept : sqrt(sqrt_) {} + + // Load norms of input data + static constexpr bool use_norms = true; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize + ((Policy::Mblk + Policy::Nblk) * sizeof(DataT)); + } + + DI void core(AccT& acc, DataT& x, DataT& y) const { acc += x * y; }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + DataT accVal = acc[i][j]; + DataT val = regxn[i] + regyn[j] - (DataT)2.0 * accVal; + + /** + * Self-neighboring points should have (aNorm == bNorm) == accVal and the dot product + * (accVal) can sometimes have round-off errors, which will cause (aNorm == bNorm) ~ accVal + * instead. + */ + acc[i][j] = + val * (val > 0) * !((val * val < get_clamp_precision()) * (regxn[i] == regyn[j])); + } + } + if (sqrt) { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] = raft::sqrt(acc[i][j]); + } + } + } + } + + constexpr l2_exp_cutlass_op get_cutlass_op() const + { + return l2_exp_cutlass_op(sqrt); + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/l2_unexp.cuh b/cpp/src/distance/detail/distance_ops/l2_unexp.cuh new file mode 100644 index 0000000000..aa6cc27f3e --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/l2_unexp.cuh @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the unexpanded euclidean distance matrix calculation + * + * It computes the following equation: + * + * c_ij = optional_sqrt ( sum_k (x_ik - y_kj)^2 ) + */ +template +struct l2_unexp_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + bool sqrt; + + l2_unexp_distance_op(bool sqrt_) noexcept : sqrt(sqrt_) {} + + // Do not load norms of data, the computation of L1 distance does not use them. + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const + { + const auto diff = x - y; + acc += diff * diff; + }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + if (sqrt) { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] = raft::sqrt(acc[i][j]); + } + } + } + }; +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/l_inf.cuh b/cpp/src/distance/detail/distance_ops/l_inf.cuh new file mode 100644 index 0000000000..d8f9384d7e --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/l_inf.cuh @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the L_inf (Chebyshev) distance matrix calculation + * + * It computes the following equation: + * + * c_ij = max_k | x_ik - y_kj | + */ +template +struct l_inf_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const + { + const auto diff = raft::abs(x - y); + acc = raft::max(acc, diff); + }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + return; + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/lp_unexp.cuh b/cpp/src/distance/detail/distance_ops/lp_unexp.cuh new file mode 100644 index 0000000000..6136f9f3eb --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/lp_unexp.cuh @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include // raft::pow, raft::abs +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the unexpanded Lp (Minkowski) distance matrix calculation + * + * It computes the following equation: + * + * c_ij = (sum_k |x_ik - y_jk|^p)^(1/p) + */ +template +struct lp_unexp_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + DataT p; + + lp_unexp_distance_op(DataT p_) noexcept : p(p_) {} + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = true; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const + { + const auto diff = raft::abs(x - y); + acc += raft::pow(diff, p); + }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + const auto one_over_p = 1.0f / p; +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] = raft::pow(acc[i][j], one_over_p); + } + } + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/russel_rao.cuh b/cpp/src/distance/detail/distance_ops/russel_rao.cuh new file mode 100644 index 0000000000..5dffdcdb89 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/russel_rao.cuh @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // DI + +namespace cuvs::distance::detail::ops { + +/** + * @brief the Russell Rao distance matrix calculation + * + * It computes the following equation: + * + * c_ij = (k - (sum_k x_ik * y_kj)) / k + */ +template +struct russel_rao_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + IdxT k; + const float one_over_k; + + russel_rao_distance_op(IdxT k_) noexcept : k(k_), one_over_k(1.0f / k_) {} + + // Load norms of input data + static constexpr bool use_norms = false; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const { acc += x * y; }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { +#pragma unroll + for (int i = 0; i < Policy::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < Policy::AccColsPerTh; ++j) { + acc[i][j] = (k - acc[i][j]) * one_over_k; + } + } + } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/distance_ops/template.cuh b/cpp/src/distance/detail/distance_ops/template.cuh new file mode 100644 index 0000000000..bdb9332379 --- /dev/null +++ b/cpp/src/distance/detail/distance_ops/template.cuh @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // DI + +namespace cuvs::distance::detail::ops { + +// Describes the computation the template distance +// +// Fill in the TODO items. + +template +struct template_distance_op { + using DataT = DataType; + using AccT = AccType; + using IdxT = IdxType; + + TODO member; + + template_distance_op(TODO member_) noexcept : member(member_) {} + + // Load norms of input data + static constexpr bool use_norms = TODO; + // Whether the core function requires so many instructions that it makes sense + // to reduce loop unrolling, etc. We do this to keep compile times in check. + static constexpr bool expensive_inner_loop = false; + + // Size of shared memory. This is normally decided by the kernel policy, but + // some ops such as correlation_distance_op use more. + template + static constexpr size_t shared_mem_size() + { + return Policy::SmemSize + TODO; + } + + DI void core(AccT& acc, DataT& x, DataT& y) const { TODO; }; + + template + DI void epilog(AccT acc[Policy::AccRowsPerTh][Policy::AccColsPerTh], + DataT* regxn, + DataT* regyn, + IdxT gridStrideX, + IdxT gridStrideY) const + { + TODO; + } + + // If exist, returns a cutlass op that performs the same operation. + // See cosine and l2_exp distance ops for an example. + constexpr l2_exp_cutlass_op get_cutlass_op() const { TODO; } +}; + +} // namespace cuvs::distance::detail::ops diff --git a/cpp/src/distance/detail/fused_distance_nn.cuh b/cpp/src/distance/detail/fused_distance_nn.cuh new file mode 100644 index 0000000000..1bf8793fdc --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn.cuh @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "distance_ops/l2_exp.cuh" // ops::l2_exp_distance_op +#include "fused_distance_nn/cutlass_base.cuh" +#include "fused_distance_nn/fused_cosine_nn.cuh" +#include "fused_distance_nn/fused_l2_nn.cuh" +#include "fused_distance_nn/helper_structs.cuh" +#include "fused_distance_nn/simt_kernel.cuh" +#include "pairwise_distance_base.cuh" // PairwiseDistances +#include +#include // raft::KeyValuePair +#include // raft::identity_op +#include // Policy +#include // raft::util::arch::SM_* +#include // raft::ceildiv, raft::shfl + +#include // size_t +#include // std::numeric_limits + +namespace cuvs { +namespace distance { + +namespace detail { + +template +void fusedDistanceNNImpl(OutT* min, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + int* workspace, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + bool sqrt, + bool initOutBuffer, + bool isRowMajor, + cuvs::distance::DistanceType metric, + float metric_arg, + cudaStream_t stream) +{ + // The kernel policy is determined by fusedDistanceNN. + typedef Policy P; + + dim3 blk(P::Nthreads); + auto nblks = raft::ceildiv(m, P::Nthreads); + constexpr auto maxVal = std::numeric_limits::max(); + typedef raft::KeyValuePair KVPair; + + RAFT_CUDA_TRY(cudaMemsetAsync(workspace, 0, sizeof(int) * m, stream)); + if (initOutBuffer) { + initKernel + <<>>(min, m, maxVal, redOp); + RAFT_CUDA_TRY(cudaGetLastError()); + } + + switch (metric) { + case cuvs::distance::DistanceType::CosineExpanded: + fusedCosineNN( + min, x, y, xn, yn, m, n, k, workspace, redOp, pairRedOp, sqrt, stream); + break; + case cuvs::distance::DistanceType::L2SqrtExpanded: + case cuvs::distance::DistanceType::L2Expanded: + // initOutBuffer is take care by fusedDistanceNNImpl() so we set it false to fusedL2NNImpl. + fusedL2NNImpl( + min, x, y, xn, yn, m, n, k, workspace, redOp, pairRedOp, sqrt, false, stream); + break; + default: assert("only cosine/l2 metric is supported with fusedDistanceNN\n"); break; + } +} + +} // namespace detail +} // namespace distance +} // namespace cuvs diff --git a/cpp/src/distance/detail/fused_distance_nn/custom_epilogue_with_broadcast.h b/cpp/src/distance/detail/fused_distance_nn/custom_epilogue_with_broadcast.h new file mode 100644 index 0000000000..186715851b --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/custom_epilogue_with_broadcast.h @@ -0,0 +1,668 @@ +/*************************************************************************************************** + * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************************/ + +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! \file + + \brief Epilogue for threadblock scoped GEMMs using Tensor Ops. + + The epilogue rearranges the result of a matrix product through shared memory to match canonical + tensor layouts in global memory. Epilogues support conversion and reduction operations. + +This file contains a customized version of EpilogueWithBroadcast from CUTLASS 2.9.1 +(https://github.com/NVIDIA/cutlass/blob/v2.9.1/include/cutlass/epilogue/threadblock/epilogue_with_broadcast.h) + +Changes: +- customized the compute_source_needed_() and apply_output_operator_() to suit the needs of per row +reduction +*/ + +#pragma once + +#if defined(__CUDACC_RTC__) +#include +#include +#else +#include + +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace epilogue { +namespace threadblock { + +///////////////////////////////////////////////////////////////////////////////////////////////// + +/// This base class is meant to define the concept required of the +/// EpilogueWithBroadcast::OutputOp +template +struct EpilogueWithBroadcastOpBaseCustom { + using ElementOutput = ElementC_; + using ElementAccumulator = ElementAccumulator_; + using ElementCompute = ElementCompute_; + using ElementZ = ElementZ_; + using ElementT = ElementT_; + static int const kElementsPerAccess = ElementsPerAccess; + + using FragmentAccumulator = Array; + using FragmentCompute = Array; + using FragmentC = Array; + using FragmentZ = Array; + using FragmentT = Array; + + /// If true, the 'Z' tensor is stored + static bool const kStoreZ = StoreZ; + + /// If true, the 'T' tensor is stored + static bool const kStoreT = StoreT; + + /// Parameters structure - required + struct Params {}; + + // + // Methods + // + + /// Constructor from Params + EpilogueWithBroadcastOpBaseCustom(Params const& params_) {} + + /// Determine if the source is needed. May return false if + bool is_source_needed() const { return true; } + + CUTLASS_HOST_DEVICE + void set_k_partition(int k_partition, int k_partition_count) {} + + /// Applies the operation when is_source_needed() is true + CUTLASS_HOST_DEVICE + void operator()(FragmentZ& frag_Z, + FragmentT& frag_T, + FragmentAccumulator const& AB, + FragmentC const& frag_C, + FragmentCompute const& V) const + { + } + + /// Applies the operation when is_source_needed() is false + CUTLASS_HOST_DEVICE + void operator()(FragmentZ& frag_Z, + FragmentT& frag_T, + FragmentAccumulator const& AB, + FragmentCompute const& V) const + { + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +/// Epilogue operator with bias vector broadcast over columns. +/// +/// Computes the following: +/// +/// +/// Z, T = OutputOp(AB, C, Broadcast) +/// +/// if (ElementwiseOp::kStoreZ) { +/// store(converted_u); +/// } +/// +/// if (ElementwiseOp::kStoreT) { +/// store(v); +/// } +/// +template < + typename Shape_, ///< Shape of threadblock tile (concept: GemmShape) + typename WarpMmaOperator_, ///< Warp-level MMA operator (concept: gemm::warp::MmaTensorOp) + int PartitionsK, ///< Number of partitions of the K dimension + typename OutputTileIterator_, ///< Tile iterator reading and writing output tensors (z) + typename TensorTileIterator_, ///< Additional tile iterator for tensor-valued operands (t) + typename ElementVector_, ///< Pointer to broadcast vector + typename AccumulatorFragmentIterator_, ///< Fragment iterator selecting accumulators + typename WarpTileIterator_, ///< Warp-scoped tile iterator writing accumulators to SMEM + typename SharedLoadIterator_, ///< Threadblock-scoped tile iterator loading from SMEM + typename OutputOp_, ///< Output operator - concept is EpilogueWithBroadcastOp + typename Padding_, ///< Padding added to SMEM allocation to avoid bank conflicts (concept: + ///< MatrixShape) + int FragmentsPerPartition = 1, ///< Used to coarsten the epilogue granularity + int IterationsUnroll = ///< Used to reduce binary size when epilogue op is large + (!IsEpilogueFunctorHeavy::value)> +class EpilogueWithBroadcastCustom : public EpilogueBase { + public: + using Base = EpilogueBase; + + using Shape = Shape_; + using WarpMmaOperator = WarpMmaOperator_; + static int const kPartitionsK = PartitionsK; + using OutputTileIterator = OutputTileIterator_; + using TensorTileIterator = TensorTileIterator_; + using ElementVector = ElementVector_; + using AccumulatorFragmentIterator = AccumulatorFragmentIterator_; + using WarpTileIterator = WarpTileIterator_; + using SharedLoadIterator = SharedLoadIterator_; + using OutputOp = OutputOp_; + using Padding = Padding_; + + using Layout = layout::RowMajor; + using LongIndex = typename Layout::LongIndex; + + /// The complete warp-level accumulator tile + using AccumulatorTile = typename Base::AccumulatorTile; + + /// Accumulator element + using ElementAccumulator = typename WarpTileIterator::Element; + + /// Compute data type produced by the output op + using ElementCompute = typename OutputOp::ElementCompute; + + /// Compute fragment + using FragmentCompute = Array; + + /// Thread map used by output tile iterators + using ThreadMap = typename OutputTileIterator::ThreadMap; + + /// Fragment object used to store the broadcast values + using BroadcastFragment = + Array; + + /// Output element + using ElementOutput = typename OutputTileIterator::Element; + + /// Data type of additional tensor + using ElementTensor = typename TensorTileIterator::Element; + + /// Output access size + static int const kElementsPerAccess = OutputTileIterator::kElementsPerAccess; + + /// Tensor reference to destination tensor + using TensorRef = typename OutputTileIterator::TensorRef; + + /// Tensor reference to sync tensor + using SyncTensorRef = typename cutlass::TensorRef; + + /// Const tensor reference to source tensor + using ConstTensorRef = typename OutputTileIterator::ConstTensorRef; + + /// Array type used to output + using OutputAccessType = + Array; + + /// Array type used by output functor + using AccumulatorAccessType = + Array; + + /// Array type used by output functor + using ComputeAccessType = Array; + + /// Tensor access type + using TensorAccessType = Array; + + /// Number of warps + using WarpCount = typename Base::WarpCount; + + /// Shared memory allocation from epilogue base class + using BaseSharedStorage = typename Base::SharedStorage; + + static int constexpr kSmemTiles = + Base::kFragmentsPerIteration > 1 ? Base::kFragmentsPerIteration : kPartitionsK; + static int constexpr kSmemPointerOffset = Base::SharedStorage::StorageShape::kCount / kSmemTiles; + + /// Used for the broadcast + struct BroadcastDetail { + /// Number of threads per warp + static int const kWarpSize = 32; + + static int const kElementsPerAccess = ThreadMap::kElementsPerAccess; + + /// Number of distinct scalar column indices handled by each thread + static int const kColumnsPerThread = + ThreadMap::Iterations::kColumn * ThreadMap::kElementsPerAccess; + + /// Number of distinct scalar row indices handled by each thread + static int const kRowsPerThread = + ThreadMap::Iterations::kCount / ThreadMap::Iterations::kColumn; + + /// Number of threads per threadblock + static int const kThreadCount = kWarpSize * WarpCount::kCount; + + /// Number of distinct threads per row of output tile + static int const kThreadsPerRow = (Shape::kN / kColumnsPerThread); + + /// Number of distinct threads which must be reduced during the final reduction phase within the + /// threadblock. + static int const kThreadRows = kThreadCount / kThreadsPerRow; + + /// I'm not sure what I meant here. + static int const kThreadAccessesPerRow = + const_max(1, (Shape::kN + kThreadCount - 1) / kThreadCount); + + /// Shape of the shared memory allocation for the epilogue + using StorageShape = MatrixShape; + + /// Debug printing + CUTLASS_DEVICE + static void print() + { +#if 0 + printf("BroadcastDetail {\n"); + printf( + " kColumnsPerThread: %d\nkRowsPerThread: %d\n,kThreadCount: %d\nkThreadsPerRow: %d\n" + "kThreadRows: %d\nThreadAccessesPerRow: %d\nStorageShape: %d x %d (count: %d)\n", + kColumnsPerThread, + kRowsPerThread, + kThreadCount, + kThreadsPerRow, + kThreadRows, + kThreadAccessesPerRow, + StorageShape::kRow, + StorageShape::kColumn, + StorageShape::kCount + ); + printf("};\n"); +#endif + } + }; + + /// Shared storage structure (shadows base) with additional SMEM buffer for reduction + struct SharedStorage { + union { + BaseSharedStorage base; + }; + + CUTLASS_HOST_DEVICE + SharedStorage() {} + }; + + public: + static_assert(SharedLoadIterator::Fragment::kElements == TensorTileIterator::Fragment::kElements, + "Mismatch between shared load iterator and output tile iterator."); + + static_assert(OutputTileIterator::kElementsPerAccess, + "OutputTileIterator::kElementsPerAccess must not be zero."); + + static_assert(!(OutputTileIterator::Fragment::kElements % OutputTileIterator::kElementsPerAccess), + "Divisibility"); + + private: + /// Loads fragment from shared memory aligned with output tensor + SharedLoadIterator shared_load_iterator_; + + /// Thread index within the threadblock + int thread_idx_; + + public: + /// Constructor + CUTLASS_DEVICE + EpilogueWithBroadcastCustom(SharedStorage& shared_storage, ///< Shared storage object + int thread_idx, ///< ID of a thread within the threadblock + int warp_idx, ///< ID of warp within threadblock + int lane_idx ///< Id of thread within warp + ) + : Base(shared_storage.base, thread_idx, warp_idx, lane_idx), + shared_load_iterator_(shared_storage.base.reference(), thread_idx), + thread_idx_(thread_idx) + { + } + + /// Streams the result to global memory + CUTLASS_DEVICE + void operator()( + OutputOp const& output_op, ///< Output operator + ElementVector const* broadcast_ptr, ///< Broadcast vector + AccumulatorTile const& accumulators, ///< Complete warp-level accumulator tile + OutputTileIterator source_iterator, ///< Tile iterator for source accumulator matrix + TensorTileIterator + tensor_iterator, ///< Threadblock tile iterator for additional tensor operand + MatrixCoord const& + problem_size = ///< Problem size needed to guard against out-of-bounds accesses + MatrixCoord(Shape::kM, Shape::kN), + MatrixCoord const& + threadblock_offset = ///< Threadblock's initial offset within the problem size space + MatrixCoord()) + { + BroadcastFragment broadcast_fragment; + + load_broadcast_fragment_(broadcast_fragment, broadcast_ptr, problem_size, threadblock_offset); + + compute_source_needed_( + output_op, broadcast_fragment, accumulators, source_iterator, tensor_iterator); + } + + private: + CUTLASS_DEVICE + void load_broadcast_fragment_( + BroadcastFragment& + broadcast_fragment, ///< Fragment containing the accumulated partial reduction over columns + ElementVector const* broadcast_ptr, ///< Broadcast vector + MatrixCoord const& + problem_size, ///< Problem size needed to guard against out-of-bounds accesses + MatrixCoord const& + threadblock_offset ///< Threadblock's initial offset within the problem size space + ) + { + broadcast_fragment.clear(); + + // If no pointer is supplied, set with all zeros and avoid memory accesses + if (!broadcast_ptr) { return; } + + int thread_initial_column = ThreadMap::initial_offset(thread_idx_).column(); + + int thread_column_idx = threadblock_offset.column() + thread_initial_column; + broadcast_ptr += thread_initial_column; + + NumericArrayConverter + converter; + using AccessType = AlignedArray; + using ComputeFragmentType = Array; + + ComputeFragmentType* frag_ptr = reinterpret_cast(&broadcast_fragment); + + CUTLASS_PRAGMA_UNROLL + for (int j = 0; j < ThreadMap::Iterations::kColumn; ++j) { + AccessType loaded; + + loaded.clear(); + + if (thread_column_idx < problem_size.column()) { + loaded = *reinterpret_cast(broadcast_ptr); + } + + ComputeFragmentType cvt = converter(loaded); + frag_ptr[j] = cvt; + + thread_column_idx += ThreadMap::Delta::kColumn; + broadcast_ptr += ThreadMap::Delta::kColumn; + } + } + + template + struct acc2smem_source_not_needed; + + template + struct acc2smem_source_not_needed> { + template + CUTLASS_DEVICE static void helper(AccumulatorFragmentIterator accum_fragment_iterator, + WarpTileIterator& warp_tile_iterator) + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < Advance; i++) { + ++accum_fragment_iterator; + } + + CUTLASS_PRAGMA_UNROLL + for (int p = 0; p < Base::kFragmentsPerIteration; ++p) { + typename AccumulatorFragmentIterator::Fragment accum_fragment; + + accum_fragment_iterator.load(accum_fragment); + ++accum_fragment_iterator; + + warp_tile_iterator.store(accum_fragment); + if (p < Base::kFragmentsPerIteration - 1) { + warp_tile_iterator.add_pointer_offset(kSmemPointerOffset); + } + } + + if (Base::kFragmentsPerIteration > 1) { + warp_tile_iterator.add_pointer_offset(kSmemPointerOffset * + (1 - Base::kFragmentsPerIteration)); + } + } + + CUTLASS_DEVICE + static void push(size_t pos, + AccumulatorFragmentIterator const& iterator_begin, + WarpTileIterator& warp_tile_iterator) + { + int dummy[] = { + (pos == (Seq * Base::kFragmentsPerIteration)) && + (helper(iterator_begin, warp_tile_iterator), 0)...}; + + CUTLASS_UNUSED(dummy[0]); + } + }; + + /// Streams the result to global memory + CUTLASS_DEVICE + void compute_source_not_needed_( + OutputOp const& output_op, ///< Output operator + BroadcastFragment const& + broadcast_fragment, ///< Fragment containing the accumulated partial reduction over columns + OutputTileIterator destination_iterator, ///< Tile iterator for destination + AccumulatorTile const& accumulators, ///< Complete warp-level accumulator tile + TensorTileIterator tensor_iterator ///< Threadblock tile iterator for additioanl tensor operand + ) + { + } + + template + struct acc2smem_source_needed; + + template + struct acc2smem_source_needed> { + template + CUTLASS_DEVICE static void helper(AccumulatorFragmentIterator accum_fragment_iterator, + WarpTileIterator& warp_tile_iterator) + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < Advance; i++) { + ++accum_fragment_iterator; + } + + typename AccumulatorFragmentIterator::Fragment accum_fragment; + accum_fragment_iterator.load(accum_fragment); + warp_tile_iterator.store(accum_fragment); + } + + CUTLASS_DEVICE + static void push(size_t pos, + AccumulatorFragmentIterator const& iterator_begin, + WarpTileIterator& warp_tile_iterator) + { + int dummy[] = {(pos == Seq) && (helper(iterator_begin, warp_tile_iterator), 0)...}; + } + }; + + /// Streams the result to global memory + CUTLASS_DEVICE + void compute_source_needed_( + OutputOp const& output_op, ///< Output operator + BroadcastFragment const& + broadcast_fragment, ///< Fragment containing the accumulated partial reduction over columns + AccumulatorTile const& accumulators, ///< Complete warp-level accumulator tile + OutputTileIterator + source_iterator, ///< Threadblock tile coordinate in GEMM (in units of threadblock tiles) + TensorTileIterator tensor_iterator ///< Threadblock tile iterator for additioanl tensor operand + ) + { + typename OutputTileIterator::Fragment source_fragment; + source_fragment.clear(); + + // + // Iterator over warp-level accumulator fragment + // + + AccumulatorFragmentIterator accum_fragment_iterator(accumulators); + + // + // Iterate over accumulator tile + // + +#pragma unroll(IterationsUnroll ? OutputTileIterator::kIterations : 1) + for (int iter = 0; iter < OutputTileIterator::kIterations; ++iter) { + // + // Convert and store fragment + // + + //__syncthreads(); + + acc2smem_source_needed>::push( + iter, accum_fragment_iterator, this->warp_tile_iterator_); + + __syncthreads(); + + // + // Load fragments from shared memory + // + + typename SharedLoadIterator::Fragment aligned_accum_fragment[kPartitionsK]; + + shared_load_iterator_.load(aligned_accum_fragment[0]); + + // + // Apply output operation + // + + typename TensorTileIterator::Fragment frag_T; + + // + // Load the source + // + + source_iterator.load(source_fragment); + ++source_iterator; + + apply_output_operator_( + frag_T, output_op, aligned_accum_fragment[0], source_fragment, broadcast_fragment); + + // + // Conditionally store fragments + // + if (OutputOp::kStoreT) { + tensor_iterator.store(frag_T); + ++tensor_iterator; + } + } + tensor_iterator.dumpToGmem(); + } + + /// Helper to invoke the output functor over each vector of output + CUTLASS_DEVICE + void apply_output_operator_(typename TensorTileIterator::Fragment& frag_T, + OutputOp const& output_op, + typename SharedLoadIterator::Fragment const& frag_AB, + typename OutputTileIterator::Fragment const& frag_C, + BroadcastFragment const& frag_Broadcast) + { + using AccessTypeT = Array; + using AccessTypeBroadcast = Array; + + AccessTypeT* frag_T_ptr = reinterpret_cast(&frag_T); + + AccumulatorAccessType const* frag_AB_ptr = + reinterpret_cast(&frag_AB); + + OutputAccessType const* frag_C_ptr = reinterpret_cast(&frag_C); + + AccessTypeBroadcast const* frag_Broadcast_ptr = + reinterpret_cast(&frag_Broadcast); + + int const kOutputOpIterations = + TensorTileIterator::Fragment::kElements / TensorTileIterator::kElementsPerAccess; + + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kOutputOpIterations; ++i) { + output_op(frag_T_ptr[i], + frag_AB_ptr[i], + frag_C_ptr[(i / ThreadMap::Iterations::kColumn)], + frag_Broadcast_ptr[i % ThreadMap::Iterations::kColumn]); + } + } + + /// Helper to invoke the output functor over each vector of output + CUTLASS_DEVICE + void apply_output_operator_source_not_needed_( + typename OutputTileIterator::Fragment& frag_Z, + typename TensorTileIterator::Fragment& frag_T, + OutputOp const& output_op, + typename SharedLoadIterator::Fragment const& frag_AB, + BroadcastFragment const& frag_Broadcast) + { + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace threadblock +} // namespace epilogue +} // namespace cutlass + +//////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/fused_distance_nn/cutlass_base.cuh b/cpp/src/distance/detail/fused_distance_nn/cutlass_base.cuh new file mode 100644 index 0000000000..f58c48aff3 --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/cutlass_base.cuh @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#pragma GCC diagnostic ignored "-Wtautological-compare" + +// We define CUTLASS_NAMESPACE in case +// RAFT cmake is not used +#ifndef CUTLASS_NAMESPACE +#define cutlass raft_cutlass +#endif + +#include "epilogue_elementwise.cuh" // FusedDistanceNNEpilogueElementwise +#include "gemm.h" // FusedDistanceNNGemm +#include // getMultiProcessorCount +#include // RAFT_CUTLASS_TRY + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cuvs { +namespace distance { +namespace detail { + +template +RAFT_KERNEL initBinMutexKernel(cuda::binary_semaphore* mut, IdxT m) +{ + auto tid = IdxT(blockIdx.x) * blockDim.x + threadIdx.x; + + if (tid < m) { mut[tid].release(); } +} + +template +void cutlassFusedDistanceNN(const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + IdxT lda, + IdxT ldb, + IdxT ldd, + OutT* dOutput, + int* mutexes, + CGReduceOpT cg_reduce_op, + DistanceFn dist_op, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + cudaStream_t stream) +{ + using EpilogueOutputOp = cutlass::epilogue::thread::FusedDistanceNNEpilogueElementwise< + DataT, // ElementC_ + AccT, // ElementAccumulator_ + DataT, // ElementCompute_ + AccT, // ElementZ_ + OutT, // ElementT_ + // 128 / cutlass::sizeof_bits::value, + 1, // Elements per access 1 + DistanceFn, + CGReduceOpT, + ReduceOpT, + KVPReduceOpT>; + constexpr int batch_count = 1; + + rmm::device_uvector> bin_mutex(m, stream); + + int blks_ = (m / 256) + 1; + + initBinMutexKernel<<>>(bin_mutex.data(), m); + + typename EpilogueOutputOp::Params epilog_op_param( + dist_op, cg_reduce_op, redOp, pairRedOp, mutexes, bin_mutex.data()); + + // Number of pipelines you want to use + constexpr int NumStages = 3; + // Alignment + constexpr int Alignment = VecLen; + + // default initialize problem size with row major inputs + auto problem_size = cutlass::gemm::GemmCoord(m, n, k); + + constexpr bool isRowMajor = true; + + using fusedDistanceNNKernel = + typename cutlass::gemm::kernel::FusedDistanceNNGemm::GemmKernel; + + using fusedDistanceNN = cutlass::gemm::device::GemmGrouped; + + int num_blocks_per_sm = fusedDistanceNN::maximum_active_blocks(); + int num_sms = raft::getMultiProcessorCount(); + int full_wave = num_blocks_per_sm * num_sms; + constexpr int mmaShapeM = fusedDistanceNNKernel::Mma::Shape::kM; + constexpr int mmaShapeN = fusedDistanceNNKernel::Mma::Shape::kN; + int columnTiles = (problem_size.n() - 1 + mmaShapeN) / mmaShapeN; + int rowTiles = (problem_size.m() - 1 + mmaShapeM) / mmaShapeM; + int totalTiles = columnTiles * rowTiles; + int thread_blocks = + rowTiles < full_wave ? (totalTiles < full_wave ? totalTiles : full_wave) : rowTiles; + + typename fusedDistanceNN::Arguments arguments{ + problem_size, + batch_count, // num of problems. + thread_blocks, + epilog_op_param, + x, + y, + xn, // C matrix eq vector param, which here is A norm + (DataT*)yn, // this is broadcast vec, which is required to be non-const param + dOutput, // Output distance matrix + (int64_t)lda, // stride A + (int64_t)ldb, // stride B + (int64_t)1, // stride A norm + (int64_t)ldd // stride Output matrix + }; + + // Using the arguments, query for extra workspace required for matrix multiplication computation + size_t workspace_size = fusedDistanceNN::get_workspace_size(arguments); + // Allocate workspace memory + rmm::device_uvector workspace(workspace_size, stream); + // Instantiate CUTLASS kernel depending on templates + fusedDistanceNN fusedDistanceNN_op; + // Check the problem size is supported or not + RAFT_CUTLASS_TRY(fusedDistanceNN_op.can_implement(arguments)); + // Initialize CUTLASS kernel with arguments and workspace pointer + RAFT_CUTLASS_TRY(fusedDistanceNN_op.initialize(arguments, workspace.data(), stream)); + // Launch initialized CUTLASS kernel + RAFT_CUTLASS_TRY(fusedDistanceNN_op.run(stream)); +} + +}; // namespace detail +}; // namespace distance +}; // namespace cuvs + +#pragma GCC diagnostic pop diff --git a/cpp/src/distance/detail/fused_distance_nn/epilogue.cuh b/cpp/src/distance/detail/fused_distance_nn/epilogue.cuh new file mode 100644 index 0000000000..06939e2bfd --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/epilogue.cuh @@ -0,0 +1,134 @@ +/*************************************************************************************************** + * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************************/ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! \file + \brief Epilogue for threadblock scoped GEMMs using Tensor Ops. + +This is adapted from DefaultEpilogueWithBroadcastTensorOp from CUTLASS 2.9.0 +(https://github.com/NVIDIA/cutlass/blob/master/include/cutlass/epilogue/threadblock/default_epilogue_with_broadcast.h#L75) + +This epilogue allows us to load norm buffers using PredicatedTileIteratorNormVec +and EpilogueWithBroadcast used for distances L2/cosine as well as applies user-define elementwise +operation. +-- A norm load is provided PredicatedTileIteratorNormVec +-- B norm load is provided by EpilogueWithBroadcast +-- elementwise operation is provided by OutputOp +*/ + +#pragma once + +#include "custom_epilogue_with_broadcast.h" +#include "predicated_tile_iterator_normvec_smem.h" +#include "predicated_tile_iterator_reduced_vec.h" + +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace epilogue { +namespace threadblock { + +//////////////////////////////////////////////////////////////////////////////// + +/// Defines sensible defaults for epilogues for TensorOps. +template +struct FusedDistanceNNEpilogue { + /// Use defaults related to the existing epilogue + using Base = + DefaultEpilogueTensorOp; + + // + // Stores the result z = (y = GEMM(A, B, C), broadcast) + // + using RowNormTileIterator = cutlass::epilogue::threadblock:: + PredicatedTileIteratorNormVecSmem; + + // + // Additional tensor tile iterator - stores t = Elementwise(z) + // + using OutputTileIterator = cutlass::epilogue::threadblock::PredicatedTileIteratorReducedVec< + typename Base::OutputTileThreadMap, + ElementTensor, + LayoutT, + typename OutputOp::Params>; + + /// Define the epilogue + using Epilogue = cutlass::epilogue::threadblock::EpilogueWithBroadcastCustom< + Shape, + WarpMmaTensorOp, + PartitionsK, + RowNormTileIterator, + OutputTileIterator, + ElementVector, + typename Base::AccumulatorFragmentIterator, + typename Base::WarpTileIterator, + typename Base::SharedLoadIterator, + OutputOp, + typename Base::Padding, + Base::kFragmentsPerIteration>; +}; + +} // namespace threadblock +} // namespace epilogue +} // namespace cutlass + +//////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/fused_distance_nn/epilogue_elementwise.cuh b/cpp/src/distance/detail/fused_distance_nn/epilogue_elementwise.cuh new file mode 100644 index 0000000000..e69b2486df --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/epilogue_elementwise.cuh @@ -0,0 +1,220 @@ +/*************************************************************************************************** + * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************************/ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +/*! \file + \brief Functor performing distance operations used by epilogues of pairwise distance + * kernels. +* This is adapted from LinearCombinationBiasElementwise from CUTLASS 2.9.0 +* customized for applying elementwise distance formula on accumulated GEMM value +* and applying user-defined operation which can convert distance values to key-value pair. +* . +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace epilogue { +namespace thread { + +///////////////////////////////////////////////////////////////////////////////////////////////// + +/// This base class is meant to define the concept required of the +/// EpilogueWithBroadcast::OutputOp +template +class FusedDistanceNNEpilogueElementwise { + public: + using ElementOutput = ElementC_; + using ElementC = ElementC_; + using ElementAccumulator = ElementAccumulator_; + using ElementCompute = ElementCompute_; + using ElementZ = ElementZ_; + using ElementT = ElementT_; + static int const kElementsPerAccess = ElementsPerAccess; + static int const kCount = kElementsPerAccess; + + using DistanceOp = DistanceOp_; + using CGReduceOp = CGReduceOp_; + + using FragmentAccumulator = Array; + using FragmentCompute = Array; + using FragmentC = Array; + using FragmentZ = Array; + using OutValT = typename CGReduceOp::AccTypeT; + using FragmentT = Array; + + using FragmentOutput = FragmentZ; + + static bool const kIsHeavy = true; // ElementwiseOp::kIsHeavy; + + /// If true, the 'Z' tensor is stored + static bool const kStoreZ = false; // We don't store anything in Z, + + /// If true, the 'T' tensor is stored + static bool const kStoreT = true; // this is our final output storage. + + /// Host-constructable parameters structure + struct Params { + CGReduceOp_ cg_reduce_op; + DistanceOp_ dist_op_; + KVPReduceOpT_ pair_redop_; + ReduceOpT_ red_op_; + int* mutexes_; + cuda::binary_semaphore* bin_mutex_; + using CGReduceT = CGReduceOp_; + // + // Methods + // + CUTLASS_HOST_DEVICE + Params(DistanceOp_ dist_op, + CGReduceOp cg_reduce_op, + ReduceOpT_ red_op, + KVPReduceOpT_ pair_redop, + int* mutexes, + cuda::binary_semaphore* bin_mutex) + : cg_reduce_op(cg_reduce_op), + dist_op_(dist_op), + pair_redop_(pair_redop), + red_op_(red_op), + mutexes_(mutexes), + bin_mutex_(bin_mutex) + { + } + + CUTLASS_HOST_DEVICE + Params() {} + }; + + private: + // + // Data members + // + DistanceOp_ elementwise_op; + KVPReduceOpT_ pair_redop; + + public: + ReduceOpT_ red_op; + + // + // Methods + // + + /// Constructor from Params + CUTLASS_HOST_DEVICE + FusedDistanceNNEpilogueElementwise(Params const& params) + : elementwise_op(params.dist_op_), pair_redop(params.pair_redop_), red_op(params.red_op_) + { + } + + /// Returns true if source is needed + CUTLASS_HOST_DEVICE + bool is_source_needed() const + { + // we use for making sure C matrix is used for A mat norm. + return true; + } + + /// Functionally required for serial reduction in the epilogue + CUTLASS_HOST_DEVICE + void set_k_partition(int k_partition, int k_partition_count) {} + + /// Applies the operation when is_source_needed() is true + CUTLASS_HOST_DEVICE + void operator()(FragmentT& frag_T, + FragmentAccumulator const& AB, + FragmentC const& frag_C, + FragmentCompute const& V) const + { + FragmentCompute tmp_Accum = + NumericArrayConverter()(AB); + FragmentCompute tmp_C = + NumericArrayConverter()(frag_C); + FragmentCompute result_Z; + + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kElementsPerAccess; ++i) { + ElementCompute res_Z = elementwise_op(tmp_C[i], V[i], tmp_Accum[i]); + frag_T[i] = res_Z; + } + } + + /// Applies the operation when is_source_needed() is false + CUTLASS_HOST_DEVICE + void operator()(FragmentZ& frag_Z, + FragmentT& frag_T, + FragmentAccumulator const& AB, + FragmentCompute const& V) const + { + } +}; + +///////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace thread +} // namespace epilogue +} // namespace cutlass + +///////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/fused_distance_nn/fused_cosine_nn.cuh b/cpp/src/distance/detail/fused_distance_nn/fused_cosine_nn.cuh new file mode 100644 index 0000000000..bcbc6689c4 --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/fused_cosine_nn.cuh @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../distance_ops/cosine.cuh" // ops::l2_exp_distance_op +#include "../pairwise_distance_base.cuh" // PairwiseDistances +#include "cutlass_base.cuh" +#include "helper_structs.cuh" +#include "simt_kernel.cuh" +#include // raft::KeyValuePair +#include // raft::identity_op +#include // Policy +#include // raft::util::arch::SM_* +#include // raft::ceildiv, raft::shfl + +#include // size_t +#include // std::numeric_limits + +namespace cuvs { +namespace distance { + +namespace detail { + +template +void fusedCosineNN(OutT* min, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + int* workspace, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + bool sqrt, + cudaStream_t stream) +{ + // The kernel policy is determined by fusedL2NN. + typedef Policy P; + + dim3 blk(P::Nthreads); + constexpr auto maxVal = std::numeric_limits::max(); + typedef raft::KeyValuePair KVPair; + + namespace arch = raft::util::arch; + using AccT = DataT; + ops::cosine_distance_op distance_op{}; + + raft::identity_op fin_op{}; + + auto kernel = fusedDistanceNNkernel; + + // Get pointer to fp32 SIMT kernel to determine the runtime architecture of the + // current system. Other methods to determine the architecture (that do not + // require a pointer) can be error prone. See: + // https://github.com/NVIDIA/cub/issues/545 + void* kernel_ptr = reinterpret_cast(kernel); + auto runtime_arch = arch::kernel_virtual_arch(kernel_ptr); + auto cutlass_range = arch::SM_range(arch::SM_80(), arch::SM_future()); + + if (cutlass_range.contains(runtime_arch)) { + // If device is SM_80 or later, use CUTLASS-based kernel. + using cosineOp = cuvs::distance::detail::ops::cosine_cutlass_op; + using kvp_cg_min_reduce_op_ = kvp_cg_min_reduce_op; + kvp_cg_min_reduce_op_ cg_reduce_op; + cosineOp cosine_dist_op; + + IdxT lda, ldb, ldd; + lda = k, ldb = k, ldd = n; + + cutlassFusedDistanceNN(x, + y, + xn, + yn, + m, + n, + k, + lda, + ldb, + ldd, + min, + workspace, + cg_reduce_op, + cosine_dist_op, + redOp, + pairRedOp, + stream); + } else { + // If device less than SM_80, use fp32 SIMT kernel. + constexpr size_t shmemSize = P::SmemSize + ((P::Mblk + P::Nblk) * sizeof(DataT)); + dim3 grid = launchConfigGenerator

(m, n, shmemSize, kernel); + + kernel<<>>( + min, x, y, xn, yn, m, n, k, maxVal, workspace, redOp, pairRedOp, distance_op, fin_op); + RAFT_CUDA_TRY(cudaGetLastError()); + } +} + +} // namespace detail +} // namespace distance +} // namespace cuvs diff --git a/cpp/src/distance/detail/fused_distance_nn/fused_l2_nn.cuh b/cpp/src/distance/detail/fused_distance_nn/fused_l2_nn.cuh new file mode 100644 index 0000000000..59fe10ea00 --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/fused_l2_nn.cuh @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../distance_ops/l2_exp.cuh" // ops::l2_exp_distance_op +#include "../pairwise_distance_base.cuh" // PairwiseDistances +#include "cutlass_base.cuh" +#include "helper_structs.cuh" +#include "simt_kernel.cuh" +#include // raft::KeyValuePair +#include // raft::identity_op +#include // Policy +#include // raft::util::arch::SM_* +#include // raft::ceildiv, raft::shfl + +#include // size_t +#include // std::numeric_limits + +namespace cuvs { +namespace distance { + +namespace detail { + +template +void fusedL2NNImpl(OutT* min, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + int* workspace, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + bool sqrt, + bool initOutBuffer, + cudaStream_t stream) +{ + // The kernel policy is determined by fusedL2NN. + typedef Policy P; + + dim3 blk(P::Nthreads); + auto nblks = raft::ceildiv(m, P::Nthreads); + constexpr auto maxVal = std::numeric_limits::max(); + typedef raft::KeyValuePair KVPair; + + RAFT_CUDA_TRY(cudaMemsetAsync(workspace, 0, sizeof(int) * m, stream)); + if (initOutBuffer) { + initKernel + <<>>(min, m, maxVal, redOp); + RAFT_CUDA_TRY(cudaGetLastError()); + } + + namespace arch = raft::util::arch; + using AccT = DataT; + ops::l2_exp_distance_op distance_op{sqrt}; + + raft::identity_op fin_op{}; + + auto kernel = fusedDistanceNNkernel; + + // Get pointer to fp32 SIMT kernel to determine the best compute architecture + // out of all for which the kernel was compiled for that matches closely + // to the current device. Other methods to determine the architecture (that do not + // require a pointer) can be error prone. See: + // https://github.com/NVIDIA/cub/issues/545 + void* kernel_ptr = reinterpret_cast(kernel); + auto runtime_arch = arch::kernel_virtual_arch(kernel_ptr); + auto cutlass_range = arch::SM_range(arch::SM_80(), arch::SM_future()); + + if (cutlass_range.contains(runtime_arch)) { + // If device is SM_80 or later, use CUTLASS-based kernel. + using L2Op = cuvs::distance::detail::ops::l2_exp_cutlass_op; + using kvp_cg_min_reduce_op_ = kvp_cg_min_reduce_op; + kvp_cg_min_reduce_op_ cg_reduce_op; + L2Op L2_dist_op(sqrt); + + IdxT lda, ldb, ldd; + lda = k, ldb = k, ldd = n; + + cutlassFusedDistanceNN(x, + y, + xn, + yn, + m, + n, + k, + lda, + ldb, + ldd, + min, + workspace, + cg_reduce_op, + L2_dist_op, + redOp, + pairRedOp, + stream); + } else { + // If device less than SM_80, use fp32 SIMT kernel. + constexpr size_t shmemSize = P::SmemSize + ((P::Mblk + P::Nblk) * sizeof(DataT)); + dim3 grid = launchConfigGenerator

(m, n, shmemSize, kernel); + + kernel<<>>( + min, x, y, xn, yn, m, n, k, maxVal, workspace, redOp, pairRedOp, distance_op, fin_op); + RAFT_CUDA_TRY(cudaGetLastError()); + } +} + +} // namespace detail +} // namespace distance +} // namespace cuvs diff --git a/cpp/src/distance/detail/fused_distance_nn/gemm.h b/cpp/src/distance/detail/fused_distance_nn/gemm.h new file mode 100644 index 0000000000..0385b95cd1 --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/gemm.h @@ -0,0 +1,409 @@ +/*************************************************************************************************** + * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************************/ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "epilogue.cuh" +#include "persistent_gemm.h" + +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace gemm { +namespace kernel { + +///////////////////////////////////////////////////////////////////////////////////////////////// +/* + * This configuration is used for float inputs with veclen(kAlignmentA/B) = 2 or 4, + * ideal threadblock tile shape is 32x256x16 for such cases as there is no + * registers spills for it. + * + */ +template < + /// Element type for A matrix operand + typename ElementA_, + /// Layout type for A matrix operand + int kAlignmentA, + /// Element type for B matrix operand + typename ElementB_, + /// Layout type for B matrix operand + int kAlignmentB, + /// Element type for C and D matrix operands + typename ElementC_, + /// Element type for internal accumulation + typename ElementAccumulator, + /// Epilogue output operator - must satisfy concept of 'EpilogueWithBroadcastOp' + typename EpilogueOutputOp, + /// Number of stages used in the pipelined mainloop + int Stages, + /// data layout row/column major of inputs + bool isRowMajor> +struct FusedDistanceNNGemm { + // This struct is specialized for fp32/3xTF32 + + /// Threadblock-level tile size (concept: GemmShape) + // <- threadblock tile M = 32, N = 256, K = 16 + // this is more performant but note that for veclen = 1 + // this shape has register spills + using ThreadblockShape = cutlass::gemm::GemmShape<32, 256, 16>; + + // <- threadblock tile M = 32, N = 128, K = 16 + // this shape has high occupancy but less perf + // this is less performant but this shape has *no* register spills + // for any veclens(1, 2, 4) + // using ThreadblockShape = cutlass::gemm::GemmShape<32, 128, 16>; + + /// Warp-level tile size (concept: GemmShape) + // This code section describes tile size a warp will compute + // <- warp tile M = 64, N = 64, K = 16 + // this is more performant for veclen 2,4. + using WarpShape = cutlass::gemm::GemmShape<32, 64, 16>; + + // this shape has high occupancy but less perf used for 32x128x16 + // using WarpShape = cutlass::gemm::GemmShape<32, 32, 16>; + + /// Warp-level tile size (concept: GemmShape) + // This code section describes the size of MMA op + // <- MMA Op tile M = 16, N = 8, K = 4 + using InstructionShape = cutlass::gemm::GemmShape<16, 8, 4>; + + /// Operation performed by GEMM + using Operator = cutlass::arch::OpMultiplyAddFastF32; + // using Operator = cutlass::arch::OpMultiplyAdd; // this runs only 1xTF32 for float inputs + + // This code section describes whether you want to use tensor cores or regular SIMT cores on GPU + // SM + using OperatorClass = cutlass::arch::OpClassTensorOp; + + // This code section describes CUDA SM architecture number + using ArchTag = cutlass::arch::Sm80; + + // This code section describes how threadblocks are scheduled on GPU + /// Threadblock-level swizzling operator + using ThreadblockSwizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>; + + /// data layout for final output matrix. + // we keep this same layout even for column major inputs + using LayoutOutput = cutlass::layout::RowMajor; + + typedef typename std::conditional::type NormXLayout; + + typedef typename std:: + conditional::type LayoutA_; + + typedef typename std:: + conditional::type LayoutB_; + + using GemmBase = typename DefaultGemmUniversal::GemmKernel; + + // Replace epilogue + using Epilogue = typename cutlass::epilogue::threadblock::FusedDistanceNNEpilogue< + typename GemmBase::Epilogue::Shape, + typename GemmBase::Epilogue::WarpMmaOperator, + GemmBase::Epilogue::kPartitionsK, + ElementAccumulator, + typename EpilogueOutputOp::ElementT, + ElementAccumulator, + EpilogueOutputOp, + NormXLayout, + GemmBase::Epilogue::kElementsPerAccess>::Epilogue; + + // Compose the GEMM kernel + using GemmKernel = FusedDistanceNNPersistent; +}; + +/* + * This configuration is used for float inputs with veclen(kAlignmentA/B) = 1, + * ideal threadblock tile shape is 32x128x16 for such cases as there is no + * registers spills for it. + * + */ +template < + /// Element type for C and D matrix operands + typename ElementC_, + /// Element type for internal accumulation + typename ElementAccumulator, + /// Epilogue output operator - must satisfy concept of 'EpilogueWithBroadcastOp' + typename EpilogueOutputOp, + /// Number of stages used in the pipelined mainloop + int Stages, + /// data layout row/column major of inputs + bool isRowMajor> +struct FusedDistanceNNGemm { + // This struct is specialized for fp32/3xTF32 + using ElementA_ = float; + using ElementB_ = float; + + /// Threadblock-level tile size (concept: GemmShape) + // <- threadblock tile M = 32, N = 128, K = 16 + // this shape has high occupancy and no register spills for veclen = 1. + using ThreadblockShape = cutlass::gemm::GemmShape<32, 128, 16>; + + /// Warp-level tile size (concept: GemmShape) + // This code section describes tile size a warp will compute + // <- warp tile M = 32, N = 32, K = 16 + using WarpShape = cutlass::gemm::GemmShape<32, 32, 16>; + + /// Warp-level tile size (concept: GemmShape) + // This code section describes the size of MMA op + // <- MMA Op tile M = 16, N = 8, K = 4 + using InstructionShape = cutlass::gemm::GemmShape<16, 8, 4>; + + /// Operation performed by GEMM + using Operator = cutlass::arch::OpMultiplyAddFastF32; + // using Operator = cutlass::arch::OpMultiplyAdd; // this runs only 1xTF32 for float inputs + + // This code section describes whether you want to use tensor cores or regular SIMT cores on GPU + // SM + using OperatorClass = cutlass::arch::OpClassTensorOp; + + // This code section describes CUDA SM architecture number + using ArchTag = cutlass::arch::Sm80; + + // This code section describes how threadblocks are scheduled on GPU + /// Threadblock-level swizzling operator + using ThreadblockSwizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>; + + /// data layout for final output matrix. + // we keep this same layout even for column major inputs + using LayoutOutput = cutlass::layout::RowMajor; + + typedef typename std::conditional::type NormXLayout; + + typedef typename std:: + conditional::type LayoutA_; + + typedef typename std:: + conditional::type LayoutB_; + + using GemmBase = typename DefaultGemmUniversal::GemmKernel; + + // Replace epilogue + using Epilogue = typename cutlass::epilogue::threadblock::FusedDistanceNNEpilogue< + typename GemmBase::Epilogue::Shape, + typename GemmBase::Epilogue::WarpMmaOperator, + GemmBase::Epilogue::kPartitionsK, + ElementAccumulator, + typename EpilogueOutputOp::ElementT, + ElementAccumulator, + EpilogueOutputOp, + NormXLayout, + GemmBase::Epilogue::kElementsPerAccess>::Epilogue; + + // Compose the GEMM kernel + using GemmKernel = FusedDistanceNNPersistent; +}; + +template < + /// Layout type for A matrix operand + int kAlignmentA, + /// Layout type for B matrix operand + int kAlignmentB, + /// Element type for C and D matrix operands + typename ElementC_, + /// Element type for internal accumulation + typename ElementAccumulator, + /// Epilogue output operator - must satisfy concept of 'EpilogueWithBroadcastOp' + typename EpilogueOutputOp, + /// Number of stages used in the pipelined mainloop + int Stages, + /// data layout row/column major of inputs + bool isRowMajor> +struct FusedDistanceNNGemm { + // Threadblock-level tile size (concept: GemmShape) + // <- threadblock tile M = 64, N = 64, K = 16 + using ThreadblockShape = cutlass::gemm::GemmShape<64, 64, 16>; + // using ThreadblockShape = cutlass::gemm::GemmShape<16, 128, 16>; + /// Warp-level tile size (concept: GemmShape) + // This code section describes tile size a warp will compute + // <- warp tile M = 32, N = 32, K = 16 + using WarpShape = cutlass::gemm::GemmShape<32, 32, 16>; + // using WarpShape = cutlass::gemm::GemmShape<16, 32, 16>; + /// Warp-level tile size (concept: GemmShape) + // This code section describes the size of MMA op + using InstructionShape = cutlass::gemm::GemmShape<8, 8, 4>; + + // Operation performed by GEMM + using Operator = cutlass::arch::OpMultiplyAdd; + // This code section describes whether you want to use tensor cores or regular SIMT cores on GPU + // SM + using OperatorClass = cutlass::arch::OpClassTensorOp; + + // This code section describes CUDA SM architecture number + using ArchTag = cutlass::arch::Sm80; + + // This code section describes how threadblocks are scheduled on GPU + /// Threadblock-level swizzling operator + using ThreadblockSwizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>; + + /// data layout for final output matrix. + // we keep this same layout even for column major inputs + using LayoutOutput = cutlass::layout::RowMajor; + + typedef typename std::conditional::type NormXLayout; + + typedef typename std:: + conditional::type LayoutA_; + + typedef typename std:: + conditional::type LayoutB_; + + using GemmBase = typename DefaultGemmUniversal::GemmKernel; + + // Replace epilogue + using Epilogue = typename cutlass::epilogue::threadblock::FusedDistanceNNEpilogue< + typename GemmBase::Epilogue::Shape, + typename GemmBase::Epilogue::WarpMmaOperator, + GemmBase::Epilogue::kPartitionsK, + ElementC_, + typename EpilogueOutputOp::ElementT, + ElementC_, + EpilogueOutputOp, + NormXLayout, + GemmBase::Epilogue::kElementsPerAccess>::Epilogue; + + // Compose the GEMM kernel + using GemmKernel = FusedDistanceNNPersistent; +}; + +///////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace kernel +} // namespace gemm +} // namespace cutlass \ No newline at end of file diff --git a/cpp/src/distance/detail/fused_distance_nn/helper_structs.cuh b/cpp/src/distance/detail/fused_distance_nn/helper_structs.cuh new file mode 100644 index 0000000000..bd439e0a7d --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/helper_structs.cuh @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../distance_ops/l2_exp.cuh" // ops::l2_exp_distance_op +#include "../pairwise_distance_base.cuh" // PairwiseDistances +#include "cutlass_base.cuh" +#include "simt_kernel.cuh" +#include // raft::KeyValuePair +#include // raft::identity_op +#include // Policy +#include // raft::util::arch::SM_* +#include // raft::ceildiv, raft::shfl +#include + +#include // size_t +#include // std::numeric_limits + +namespace cuvs { +namespace distance { + +namespace detail { + +template +struct KVPMinReduceImpl { + typedef raft::KeyValuePair KVP; + DI KVP operator()(LabelT rit, const KVP& a, const KVP& b) { return b.value < a.value ? b : a; } + DI KVP operator()(const KVP& a, const KVP& b) { return b.value < a.value ? b : a; } + +}; // KVPMinReduce + +template +struct MinAndDistanceReduceOpImpl { + typedef typename raft::KeyValuePair KVP; + + DI void operator()(LabelT rid, KVP* out, const KVP& other) const + { + if (other.value < out->value) { + out->key = other.key; + out->value = other.value; + } + } + DI void operator()(LabelT rid, volatile KVP* out, const KVP& other) const + { + if (other.value < out->value) { + out->key = other.key; + out->value = other.value; + } + } + + DI void operator()(LabelT rid, DataT* out, const KVP& other) const + { + if (other.value < *out) { *out = other.value; } + } + + DI void operator()(LabelT rid, volatile DataT* out, const KVP& other) const + { + if (other.value < *out) { *out = other.value; } + } + + DI void operator()(LabelT rid, DataT* out, const DataT& other) const + { + if (other < *out) { *out = other; } + } + + DI void operator()(LabelT rid, volatile DataT* out, const DataT& other) const + { + if (other < *out) { *out = other; } + } + + DI void init(DataT* out, DataT maxVal) const { *out = maxVal; } + DI void init(KVP* out, DataT maxVal) const + { + out->value = maxVal; + out->key = 0xfffffff0; + } + + DI void init_key(DataT& out, LabelT idx) const { return; } + DI void init_key(KVP& out, LabelT idx) const { out.key = idx; } + + DI DataT get_value(KVP& out) const { return out.value; } + DI DataT get_value(DataT& out) const { return out; } +}; + +template +struct MinReduceOpImpl { + typedef typename raft::KeyValuePair KVP; + DI void operator()(LabelT rid, DataT* out, const KVP& other) + { + if (other.value < *out) { *out = other.value; } + } + + DI void init(DataT* out, DataT maxVal) { *out = maxVal; } +}; + +template +RAFT_KERNEL initKernel(OutT* min, IdxT m, DataT maxVal, ReduceOpT redOp) +{ + auto tid = IdxT(blockIdx.x) * blockDim.x + threadIdx.x; + if (tid < m) { redOp.init(min + tid, maxVal); } +} + +template +void initialize(OutT* min, IdxT m, DataT maxVal, ReduceOpT redOp, cudaStream_t stream) +{ + auto blks = raft::ceildiv(m, 256); + initKernel<<>>(min, m, maxVal, redOp); +} + +// cg::reduce functor for FusedDistanceNN used in its cutlass version +// to output the min distance value & key(loc id). +// This is used in fused_distance_nn/predicated_tile_iterator_reduced_vec.h +// store_with_byte_offset() passed to cg::reduce() & select_reduce. +template +struct kvp_cg_min_reduce_op { + typedef typename raft::KeyValuePair KVP; + + __host__ __device__ kvp_cg_min_reduce_op() noexcept {}; + + using AccTypeT = AccType; + using IndexT = Index; + // functor signature. + __host__ __device__ KVP operator()(KVP a, KVP b) const { return a.value < b.value ? a : b; } + + __host__ __device__ AccType operator()(AccType a, AccType b) const { return min(a, b); } + + __host__ __device__ bool isAmin(AccType a, AccType b) const { return a < b ? true : false; } +}; + +} // namespace detail +} // namespace distance +} // namespace cuvs diff --git a/cpp/src/distance/detail/fused_distance_nn/persistent_gemm.h b/cpp/src/distance/detail/fused_distance_nn/persistent_gemm.h new file mode 100644 index 0000000000..f1a7c728e9 --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/persistent_gemm.h @@ -0,0 +1,512 @@ +/*************************************************************************************************** + * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************************/ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! \file + \brief Problem visitor for grouped GEMMs +This file contains heavily customized version of GemmGrouped from CUTLASS 2.10.0 +(https://github.com/NVIDIA/cutlass/blob/v2.10.0/include/cutlass/gemm/kernel/gemm_grouped.h) + +Changes: +- adds support for only single problem size to be launched persistently + where each threablock processes more than one tile of the same problem. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace gemm { +namespace kernel { + +///////////////////////////////////////////////////////////////////////////////////////////////// + +template +struct FusedDistanceNNPersistent { + public: + using Mma = Mma_; + using Epilogue = Epilogue_; + using EpilogueOutputOp = typename Epilogue::OutputOp; + using ThreadblockSwizzle = ThreadblockSwizzle_; + static GroupScheduleMode const kGroupScheduleMode = GroupScheduleMode_; + static bool const kTransposed = Transposed; + + // Optional transpose + using MapArguments = kernel::detail::MapArguments; + + // Public-facing type definitions related to operand element type, layout, and complex conjugate + // operation. Must interact with the 'kTransposed' notion. + using ElementA = typename MapArguments::ElementA; + using LayoutA = typename MapArguments::LayoutA; + using ElementB = typename MapArguments::ElementB; + using LayoutB = typename MapArguments::LayoutB; + using ElementC = typename Epilogue::OutputTileIterator::Element; + using LayoutC = typename MapArguments::LayoutC; + + static ComplexTransform const kTransformA = MapArguments::kTransformA; + static ComplexTransform const kTransformB = MapArguments::kTransformB; + + // Type definitions about the mainloop. + using Operator = typename Mma::Operator; + using OperatorClass = typename Mma::Operator::OperatorClass; + using ThreadblockShape = typename Mma::Shape; + using WarpShape = typename Mma::Operator::Shape; + using InstructionShape = typename Mma::Policy::Operator::InstructionShape; + using ArchTag = typename Mma::ArchTag; + + static int const kStages = Mma::kStages; + static int const kAlignmentA = MapArguments::kAlignmentA; + static int const kAlignmentB = MapArguments::kAlignmentB; + static int const kAlignmentC = Epilogue::OutputTileIterator::kElementsPerAccess; + + /// Warp count (concept: GemmShape) + using WarpCount = typename Mma::WarpCount; + static int const kThreadCount = 32 * WarpCount::kCount; + + using ProblemVisitor = GemmGroupedProblemVisitor; + + // + // Structures + // + + struct temp_problem_visitor { + int problem_count; + + CUTLASS_HOST_DEVICE temp_problem_visitor() : problem_count(0){}; + CUTLASS_HOST_DEVICE temp_problem_visitor(int problem_count_) : problem_count(problem_count_){}; + }; + + /// Argument structure + struct Arguments { + // + // Data members + // + GemmCoord problem_sizes; + temp_problem_visitor problem_visitor; + int problem_count; + int threadblock_count; + + typename EpilogueOutputOp::Params output_op; + + void const* ptr_A; + void const* ptr_B; + void const* ptr_C; + void* ptr_Vector; + void* ptr_Tensor; + + typename LayoutA::Stride::Index lda; + typename LayoutB::Stride::Index ldb; + typename LayoutC::Stride::Index ldc; + typename LayoutC::Stride::Index ldt; + + // Only used by device-level operator + GemmCoord* host_problem_sizes; + + // + // Methods + // + + /// Default ctor + CUTLASS_HOST_DEVICE + Arguments() + : threadblock_count(0), + ptr_A(nullptr), + ptr_B(nullptr), + ptr_C(nullptr), + ptr_Vector(nullptr), + ptr_Tensor(nullptr), + lda(0), + ldb(0), + ldc(0), + ldt(0), + host_problem_sizes(nullptr) + { + } + + /// Ctor + CUTLASS_HOST_DEVICE + Arguments(GemmCoord problem_sizes, + int problem_count, + int threadblock_count, + typename EpilogueOutputOp::Params output_op, + void const* ptr_A, + void const* ptr_B, + void const* ptr_C, + void* ptr_Vector, + void* ptr_Tensor, + typename LayoutA::Stride::Index lda, + typename LayoutB::Stride::Index ldb, + typename LayoutC::Stride::Index ldc, + typename LayoutC::Stride::Index ldt, + GemmCoord* host_problem_sizes = nullptr) + : problem_sizes(problem_sizes), + threadblock_count(threadblock_count), + output_op(output_op), + ptr_A(ptr_A), + ptr_B(ptr_B), + ptr_C(ptr_C), + ptr_Vector(ptr_Vector), + ptr_Tensor(ptr_Tensor), + lda(lda), + ldb(ldb), + ldc(ldc), + ldt(ldt), + host_problem_sizes(host_problem_sizes) + { + problem_visitor.problem_count = problem_count; + } + }; + + // + // Structure for precomputing values in host memory and passing to kernels + // + + /// Parameters structure + struct Params { + temp_problem_visitor problem_visitor; + int threadblock_count; + + typename Mma::IteratorA::Params params_A; + typename Mma::IteratorB::Params params_B; + typename Epilogue::OutputTileIterator::Params params_C; + typename Epilogue::TensorTileIterator::Params params_Tensor; + + typename EpilogueOutputOp::Params output_op; + + void* ptr_A; + void* ptr_B; + void* ptr_C; + void* ptr_Vector; + void* ptr_Tensor; + + GemmCoord problem_size; + typename LayoutA::Stride::Index lda; + typename LayoutB::Stride::Index ldb; + typename LayoutC::Stride::Index ldc; + typename LayoutC::Stride::Index ldt; + + // + // Methods + // + + CUTLASS_HOST_DEVICE + Params() + : params_A(0), + params_B(0), + params_C(0), + ptr_A(nullptr), + ptr_B(nullptr), + ptr_C(nullptr), + ptr_Vector(nullptr), + ptr_Tensor(nullptr), + lda(0), + ldb(0), + ldc(0), + ldt(0) + { + } + + CUTLASS_HOST_DEVICE + Params(Arguments const& args, void* workspace = nullptr, int tile_count = 0) + : problem_size(args.problem_sizes), + threadblock_count(args.threadblock_count), + output_op(args.output_op), + params_A(args.lda), + params_B(args.ldb), + params_C(args.ldc), + // Here we pass additional user args via args.output_op + // to the reduction output tile iterator + params_Tensor(args.ldt, args.output_op), + ptr_A(const_cast(args.ptr_A)), + ptr_B(const_cast(args.ptr_B)), + ptr_C(const_cast(args.ptr_C)), + ptr_Vector(args.ptr_Vector), + ptr_Tensor(args.ptr_Tensor), + lda(args.lda), + ldb(args.ldb), + ldc(args.ldc), + ldt(args.ldt) + { + problem_visitor.problem_count = args.problem_visitor.problem_count; + } + + CUTLASS_HOST_DEVICE + void update(Arguments const& args, void* workspace = nullptr, int tile_count = 0) + { + threadblock_count = args.threadblock_count; + output_op = args.output_op; + ptr_A = const_cast(args.ptr_A); + ptr_B = const_cast(args.ptr_B); + ptr_C = const_cast(args.ptr_C); + ptr_Vector = args.ptr_Vector; + ptr_Tensor = args.ptr_Tensor; + lda = args.lda; + ldb = args.ldb; + ldc = args.ldc; + ldt = args.ldt; + + problem_size = args.problem_sizes; + } + }; + + /// Shared memory storage structure + struct SharedStorage { + union { + typename Mma::SharedStorage main_loop; + typename Epilogue::SharedStorage epilogue; + } kernel; + + typename Epilogue::TensorTileIterator::SharedStorage reduced_store; + typename Epilogue::OutputTileIterator::SharedStorage rownorm_store; + }; + + public: + // + // Methods + // + + CUTLASS_DEVICE + FusedDistanceNNPersistent() {} + + /// Determines whether kernel satisfies alignment + static Status can_implement(cutlass::gemm::GemmCoord const& problem_size) + { + return Status::kSuccess; + } + + static Status can_implement(Arguments const& args) { return Status::kSuccess; } + + static size_t get_extra_workspace_size(Arguments const& args, + cutlass::gemm::GemmCoord const& grid_tiled_shape) + { + return 0; + } + + CUTLASS_DEVICE + static uint32_t tile_count(const cutlass::MatrixCoord& grid) + { + return grid.row() * grid.column(); + } + + /// Get the grid shape + CUTLASS_DEVICE + static cutlass::MatrixCoord grid_shape(const cutlass::gemm::GemmCoord& problem) + { + return cutlass::MatrixCoord(((problem.m() - 1 + ThreadblockShape::kM) / ThreadblockShape::kM), + ((problem.n() - 1 + ThreadblockShape::kN) / ThreadblockShape::kN)); + } + + /// Executes one GEMM + CUTLASS_DEVICE + void operator()(Params const& params, SharedStorage& shared_storage) + { +#if __CUDA_ARCH__ >= 800 + // + // These types shadow the type-level definitions and support the ability to implement + // a 'transposed' GEMM that computes the transposed problems. + // + using ElementA = typename Mma::IteratorA::Element; + using LayoutA = typename Mma::IteratorA::Layout; + using ElementB = typename Mma::IteratorB::Element; + using LayoutB = typename Mma::IteratorB::Layout; + using ElementC = typename Epilogue::OutputTileIterator::Element; + using LayoutC = typename Epilogue::OutputTileIterator::Layout; + + const GemmCoord& problem_size = params.problem_size; + const auto grid_shape_ = grid_shape(problem_size); + const uint32_t problem_chunk = (tile_count(grid_shape_) - 1 + gridDim.x) / gridDim.x; + const uint32_t problem_chunk_end = blockIdx.x * problem_chunk + problem_chunk; + typename LayoutB::Index column = + ((blockIdx.x * problem_chunk) % grid_shape_.column()) * Mma::Shape::kN; + + typename LayoutB::Index row = + ((blockIdx.x * problem_chunk) / grid_shape_.column()) * Mma::Shape::kM; + if (column) { + shared_storage.reduced_store.initSmem(params.output_op); + shared_storage.rownorm_store.initSmem(params.ptr_C, problem_size.m(), row, sizeof(ElementC)); + } + + // Outer 'persistent' loop to iterate over tiles + for (uint32_t tile_idx = blockIdx.x * problem_chunk; tile_idx < problem_chunk_end; tile_idx++) { + const auto grid_shape_ = grid_shape(problem_size); + cutlass::MatrixCoord threadblock_offset( + int(tile_idx / grid_shape_.column()) * Mma::Shape::kM, + int(tile_idx % grid_shape_.column()) * Mma::Shape::kN); + + const bool isNextTile = ((tile_idx + 1) < problem_chunk_end); + const bool doesRowChange = + ((threadblock_offset.column() + Mma::Shape::kN) >= problem_size.n()); + const bool do_gmem_reduce = (doesRowChange || !isNextTile) ? true : false; + + ElementA* ptr_A = static_cast(params.ptr_A); + ElementB* ptr_B = static_cast(params.ptr_B); + + // Compute initial location in logical coordinates + cutlass::MatrixCoord tb_offset_A{threadblock_offset.row(), 0}; + cutlass::MatrixCoord tb_offset_B{0, threadblock_offset.column()}; + + // Compute position within threadblock + int thread_idx = threadIdx.x; + + // Construct iterators to A and B operands + typename Mma::IteratorA iterator_A( + params.params_A, ptr_A, {problem_size.m(), problem_size.k()}, thread_idx, tb_offset_A); + + typename Mma::IteratorB iterator_B( + params.params_B, ptr_B, {problem_size.k(), problem_size.n()}, thread_idx, tb_offset_B); + + // Broadcast the warp_id computed by lane 0 to ensure dependent code + // is compiled as warp-uniform. + int warp_idx = __shfl_sync(0xffffffff, threadIdx.x / 32, 0); + + int lane_idx = threadIdx.x % 32; + + // + // Matrix multiply phase + // + + // Construct thread-scoped matrix multiply + Mma mma(shared_storage.kernel.main_loop, thread_idx, warp_idx, lane_idx); + + typename Mma::FragmentC accumulators; + + accumulators.clear(); + // Compute threadblock-scoped matrix multiply-add + int gemm_k_iterations = (problem_size.k() + Mma::Shape::kK - 1) / Mma::Shape::kK; + + // Wait for all threads to finish their epilogue phases from the previous tile. + //__syncthreads(); + + // Compute threadblock-scoped matrix multiply-add + mma(gemm_k_iterations, accumulators, iterator_A, iterator_B, accumulators); + + // + // Epilogue + // + + EpilogueOutputOp output_op(params.output_op); + + ElementC* ptr_C = static_cast(params.ptr_C); + typename Epilogue::ElementTensor* ptr_Tensor = + static_cast(params.ptr_Tensor); + + // Define the reduction output pointer and move to the appropriate place + typename Epilogue::ElementVector* ptr_Vector = + static_cast(params.ptr_Vector); + + // Tile iterator loading from source tensor. + typename Epilogue::OutputTileIterator iterator_rownorm(shared_storage.rownorm_store, + params.params_C, + ptr_C, + problem_size.mn(), + thread_idx, + threadblock_offset); + + // Additional tensor to load from + typename Epilogue::TensorTileIterator tensor_iterator(shared_storage.reduced_store, + params.params_Tensor, + // Only the final block outputs Tensor + ptr_Tensor, + problem_size.mn(), + thread_idx, + do_gmem_reduce, + threadblock_offset); + + Epilogue epilogue(shared_storage.kernel.epilogue, thread_idx, warp_idx, lane_idx); + + // Execute the epilogue operator to update the destination tensor. + // Move to appropriate location for this output tile + if (ptr_Vector) { ptr_Vector += threadblock_offset.column(); } + + // Execute the epilogue operator to update the destination tensor. + epilogue(output_op, + ptr_Vector, + // iterator_D, + accumulators, + iterator_rownorm, + tensor_iterator, + problem_size.mn(), + threadblock_offset); + } +#endif + } +}; + +///////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace kernel +} // namespace gemm +} // namespace cutlass + +///////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_normvec_smem.h b/cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_normvec_smem.h new file mode 100644 index 0000000000..794cd5eb63 --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_normvec_smem.h @@ -0,0 +1,448 @@ +/*************************************************************************************************** + * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************************/ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! \file + \brief Epilogue for threadblock scoped GEMMs using Tensor Ops. + +This file contains a customized version of PredicatedTileIterator from CUTLASS 2.9.0 +(https://github.com/NVIDIA/cutlass/blob/v2.9.0/include/cutlass/epilogue/threadblock/predicated_tile_iterator.h#L75) + +Changes: +- added `Layout_` template param +- Only the row index is used to load the data in load_with_byte_offset(). + This way the same normalization data is used across all columns in a row. + +*/ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { + +//////////////////////////////////////////////////////////////////////////////// + +namespace epilogue { +namespace threadblock { + +//////////////////////////////////////////////////////////////////////////////// + +/// Tile iterator used to load and store output tile from global memory in epilogue. +/// +/// Satisfies: ReadableTileIterator | PredicatedTileIterator | ForwardTileIterator +/// +template +class PredicatedTileIteratorNormVecSmem { + public: + using ThreadMap = ThreadMap_; + using Shape = typename ThreadMap::Shape; + + using Element = Element_; + + using Layout = Layout_; + using TensorRef = TensorRef; + using ConstTensorRef = typename TensorRef::ConstTensorRef; + + using Index = typename Layout::Index; + using LongIndex = typename Layout::LongIndex; + using TensorCoord = MatrixCoord; + + static int const kElementsPerAccess = ThreadMap::kElementsPerAccess; + static int const kThreads = ThreadMap::kThreads; + static int const kIterations = ThreadMap::Count::kTile; + + static int const total_rows = ThreadMap::kWarpCount * ThreadMap::Iterations::kRow * + ThreadMap::Iterations::kGroup * ThreadMap::Iterations::kCluster * + ThreadMap::Count::kTile * ThreadMap::Delta::kRow; + + static_assert(ThreadMap::Iterations::kRow > 0, "ThreadMap::Iterations::kRow must be > 0"); + static_assert(ThreadMap::Iterations::kGroup > 0, "ThreadMap::Iterations::kGroup must be > 0"); + static_assert(ThreadMap::Iterations::kCluster > 0, "ThreadMap::Iterations::kCluster must be > 0"); + static_assert(ThreadMap::Iterations::kColumn > 0, "ThreadMap::Iterations::kColumn must be > 0"); + + using Fragment = Array; + + /// Memory access size + using AccessType = AlignedArray; + + // + // Parameters struct + // + + /// Uses a non-template class + struct Params : PredicatedTileIteratorParams { + using Base = PredicatedTileIteratorParams; + + CUTLASS_HOST_DEVICE + Params() {} + + CUTLASS_HOST_DEVICE + Params(Layout const& layout) + : PredicatedTileIteratorParams( + layout.stride(0) * int(sizeof(AccessType)) / kElementsPerAccess, + make_OutputTileThreadMapDesc()) + { + } + + CUTLASS_HOST_DEVICE + Params(Base const& base) : Base(base) {} + }; + + /// Mask object + struct Mask { + static int const kCount = ThreadMap::Iterations::kColumn; + + /// Predicate state + bool predicates[kCount]; + + // + // Mask + // + CUTLASS_HOST_DEVICE + Mask() { enable(); } + + ///< Efficiently disables all accesses guarded by mask + CUTLASS_HOST_DEVICE void clear() + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kCount; ++i) { + predicates[i] = false; + } + } + + ///< CUTLASS_HOST_DEVICE enables all accesses guarded by mask + CUTLASS_DEVICE void enable() + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kCount; ++i) { + predicates[i] = true; + } + } + }; + + /// Shared storage allocation needed by the predicated tile + // iterator for storing rowNorm chunk. + struct SharedStorage { + // + // Type definitions + // + using Shape = MatrixShape; + + /// Shape of the shared memory allocation + using StorageShape = MatrixShape; + + // + // Data members + // + // Methods + // + AlignedBuffer storage; + + CUTLASS_DEVICE + Element* data() { return storage.data(); } + + SharedStorage() {} + + CUTLASS_DEVICE + void initSmem(void* pointer, + const Index& num_rows, + const Index& tb_row_offset, + const LongIndex& stride) + { + Element* shared_elem_arr = data(); + uint8_t* first_tile_byte_pointer_ = + reinterpret_cast(pointer) + LongIndex(tb_row_offset) * LongIndex(stride); + const auto gmem_ptr = reinterpret_cast(first_tile_byte_pointer_); + + for (int row = threadIdx.x; row < total_rows; row += blockDim.x) { + bool guard = (tb_row_offset + row) < num_rows; + cutlass::arch::cp_async(shared_elem_arr + row, gmem_ptr + row, guard); + cutlass::arch::cp_async_wait<0>(); + } + } + }; + + private: + // + // Data members + // + + /// Parameters structure containing reference and precomputed state. + PredicatedTileIteratorParams params_; + + /// Byte-level pointer + uint8_t* byte_pointer_; + + /// Array of boolean values to contain steady-state predicates + Mask mask_; + + /// Extent of the matrix tile in rows + Index extent_row_; + + /// Extent of the matrix tile in rows + Index extent_column_; + + /// A thread's starting row position (assuming steady-state predicates have been computed) + Index thread_start_row_; + + /// A thread's starting column + Index thread_start_column_; + + /// Internal state counter + int state_[3]; + + /// Scatter indices + int const* indices_; + + // + // Static asserts about internal strides + // + + static_assert(sizeof(extent_row_) == 4, "Expected 32b extents"); + static_assert(sizeof(thread_start_row_) == 4, "Expected 32b extents"); + static_assert(sizeof(PredicatedTileIteratorParams::stride) == 8, "Expected 64b strides"); + + private: + // + // Methods + // + + protected: + SharedStorage& shared_storage_; + + public: + // + // Methods + // + + /// Constructor + CUTLASS_DEVICE + PredicatedTileIteratorNormVecSmem(SharedStorage& shared_storage, + PredicatedTileIteratorParams const& params, + Element* pointer, + TensorCoord extent, + int thread_idx, + TensorCoord& threadblock_offset, + int const* indices = nullptr) + : params_(params), indices_(indices), shared_storage_(shared_storage) + { + TensorCoord thread_offset = ThreadMap::initial_offset(thread_idx) + threadblock_offset; + + extent_row_ = extent.row(); + extent_column_ = extent.column(); + + thread_start_row_ = thread_offset.row(); + thread_start_column_ = thread_offset.column(); + + // Initialize predicates + CUTLASS_PRAGMA_UNROLL + for (int c = 0; c < ThreadMap::Iterations::kColumn; ++c) { + mask_.predicates[c] = + ((thread_offset.column() + ThreadMap::Delta::kColumn * c) < extent.column()); + } + + // Null pointer performs no accesses + if (!pointer) { + mask_.clear(); + return; + } + + if (ScatterD && !indices) { mask_.clear(); } + + // Initialize pointer + byte_pointer_ = reinterpret_cast(pointer) + + LongIndex(thread_offset.row()) * LongIndex(params_.stride); + + if (ScatterD) { + byte_pointer_ = reinterpret_cast(pointer) + + LongIndex(thread_offset.column()) * sizeof(AccessType) / kElementsPerAccess; + } + + if (threadblock_offset.column() == 0) { + shared_storage_.initSmem(pointer, extent_row_, threadblock_offset.row(), params_.stride); + } + + // Initialize internal state counter + state_[0] = state_[1] = state_[2] = 0; + } + + /// Adds a pointer offset in units of Element + CUTLASS_HOST_DEVICE + void add_pointer_offset(LongIndex pointer_offset) + { + byte_pointer_ += pointer_offset * sizeof_bits::value / 8; + } + + /// Loads a fragment from memory + CUTLASS_DEVICE + void load_with_byte_offset(Fragment& frag, int64_t byte_offset) const + { + AccessType* frag_ptr = reinterpret_cast(&frag); + + Element* shared_elem_arr = shared_storage_.data(); + + CUTLASS_PRAGMA_UNROLL + for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { + CUTLASS_PRAGMA_UNROLL + for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { + CUTLASS_PRAGMA_UNROLL + for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { + int frag_row_idx = + (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); + + int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + + cluster * ThreadMap::Delta::kCluster; + int iter_row = ((row_offset + thread_start_row_) % total_rows); + Element val = shared_elem_arr[iter_row]; + + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kElementsPerAccess; ++i) { + (*frag_ptr)[frag_row_idx + i] = val; + } + } + } + } + } + + /// Loads a fragment from memory + CUTLASS_DEVICE + void load(Fragment& frag) const { load_with_byte_offset(frag, 0); } + + CUTLASS_DEVICE + MatrixCoord thread_start() const { return MatrixCoord(thread_start_row_, thread_start_column_); } + + /// Need to get the thread start row from the tile iterator + CUTLASS_DEVICE + int32_t thread_start_row() const { return thread_start_row_; } + + /// Need to get the thread start row from the tile iterator + CUTLASS_DEVICE + int32_t thread_start_column() const { return thread_start_column_; } + + /// Extent of the matrix in rows + CUTLASS_DEVICE + Index extent_row() const { return extent_row_; } + + /// Extent of the matrix in columns + CUTLASS_DEVICE + Index extent_column() const { return extent_column_; } + + /// Advances to the next position to load or store + CUTLASS_HOST_DEVICE + PredicatedTileIteratorNormVecSmem& operator++() + { + ++state_[0]; + + if (!ScatterD) { byte_pointer_ += params_.advance_row; } + + thread_start_row_ += ThreadMap::Shape::kRow; + + if (state_[0] == ThreadMap::Count::kRow) { + state_[0] = 0; + ++state_[1]; + byte_pointer_ += params_.advance_group; + + thread_start_row_ += + (ThreadMap::Shape::kGroup - 1) * ThreadMap::Shape::kRow * ThreadMap::Count::kRow; + + if (state_[1] == ThreadMap::Count::kGroup) { + state_[1] = 0; + ++state_[2]; + byte_pointer_ += params_.advance_cluster; + + thread_start_row_ += ThreadMap::Count::kGroup * ThreadMap::Shape::kGroup * + ThreadMap::Count::kRow * ThreadMap::Shape::kRow; + + if (state_[2] == ThreadMap::Count::kCluster) { + state_[2] = 0; + byte_pointer_ += params_.advance_tile; + } + } + } + + return *this; + } + + ///< Efficiently disables all accesses guarded by mask + CUTLASS_DEVICE void clear_mask() { mask_.clear(); } + + ///< Efficiently enables all accesses guarded by mask + CUTLASS_DEVICE void enable_mask() { mask_.enable(); } + + ///< Sets the mask + CUTLASS_DEVICE void get_mask(Mask& mask) const { mask = mask_; } + + ///< Sets the mask + CUTLASS_DEVICE void set_mask(Mask const& mask) { mask_ = mask; } +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace threadblock +} // namespace epilogue +} // namespace cutlass + +//////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_reduced_vec.h b/cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_reduced_vec.h new file mode 100644 index 0000000000..5ffb74e9cd --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/predicated_tile_iterator_reduced_vec.h @@ -0,0 +1,610 @@ +/*************************************************************************************************** + * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************************/ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! \file + \brief Epilogue for threadblock scoped GEMMs using Tensor Ops. + +This file contains a customized version of PredicatedTileIterator from CUTLASS 2.9.0 +(https://github.com/NVIDIA/cutlass/blob/v2.9.0/include/cutlass/epilogue/threadblock/predicated_tile_iterator.h#L75) + +Changes: +- added `Layout_` template param +- PredicatedTileIteratorParams() is customized to not stride by layout.stride(0). +- makes use of `SharedStorage` to store reduced values across warps to gmem in coalesced manner. +- customized the store_with_byte_offset() to perform reduction per row and write final value to +gmem. +- customized the Params() struct to take user inputs from epilogueOp params. + +*/ + +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cg = cooperative_groups; + +//////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { + +//////////////////////////////////////////////////////////////////////////////// + +namespace epilogue { +namespace threadblock { + +//////////////////////////////////////////////////////////////////////////////// + +/// Tile iterator used to load and store output tile from global memory in epilogue. +/// +/// Satisfies: ReadableTileIterator | PredicatedTileIterator | ForwardTileIterator +/// +template +class PredicatedTileIteratorReducedVec { + public: + using ThreadMap = ThreadMap_; + using Shape = typename ThreadMap::Shape; + + using Element = Element_; + + using Layout = Layout_; + using TensorRef = TensorRef; + using ConstTensorRef = typename TensorRef::ConstTensorRef; + + using Index = typename Layout::Index; + using LongIndex = typename Layout::LongIndex; + using TensorCoord = MatrixCoord; + using EpilogueOpParams = EpilogueOpParams_; + using OutIdxT = typename EpilogueOpParams::CGReduceT::IndexT; + using OutValT = typename EpilogueOpParams::CGReduceT::AccTypeT; + + static int const kElementsPerAccess = ThreadMap::kElementsPerAccess; + static int const kThreads = ThreadMap::kThreads; + static int const kIterations = ThreadMap::Count::kTile; + + static_assert(ThreadMap::Iterations::kRow > 0, "ThreadMap::Iterations::kRow must be > 0"); + static_assert(ThreadMap::Iterations::kGroup > 0, "ThreadMap::Iterations::kGroup must be > 0"); + static_assert(ThreadMap::Iterations::kCluster > 0, "ThreadMap::Iterations::kCluster must be > 0"); + static_assert(ThreadMap::Iterations::kColumn > 0, "ThreadMap::Iterations::kColumn must be > 0"); + static_assert(!UseCUDAStore, "UseCUDAStore path is not supported"); + + static int const total_rows = ThreadMap::kWarpCount * ThreadMap::Iterations::kRow * + ThreadMap::Iterations::kGroup * ThreadMap::Iterations::kCluster * + ThreadMap::Count::kTile * ThreadMap::Delta::kRow; + /// Fragment object + using Fragment = + Array; + + // Memory access size + using AccessType = AlignedArray; + using AccessTypeValT = AlignedArray; + + // + // Parameters struct + // + + /// Uses a non-template class + struct Params : PredicatedTileIteratorParams { + using Base = PredicatedTileIteratorParams; + + EpilogueOpParams user_param; + CUTLASS_HOST_DEVICE + Params() {} + + CUTLASS_HOST_DEVICE + Params(Layout const& layout) + : PredicatedTileIteratorParams( + layout.stride(0) * int(sizeof(AccessType)) / kElementsPerAccess, + make_OutputTileThreadMapDesc()) + { + } + + CUTLASS_HOST_DEVICE + Params(Layout const& layout, EpilogueOpParams const& user_param_) + : PredicatedTileIteratorParams(int(sizeof(AccessType)) / kElementsPerAccess, + make_OutputTileThreadMapDesc()), + user_param(user_param_) + { + } + + CUTLASS_HOST_DEVICE + Params(Base const& base) : Base(base) {} + }; + + /// Mask object + struct Mask { + // static int const kCount = ThreadMap::Iterations::kColumn; + static int const kCount = ThreadMap::Iterations::kColumn * kElementsPerAccess; + + /// Predicate state + bool predicates[kCount]; + + // + // Mask + // + CUTLASS_HOST_DEVICE + Mask() { enable(); } + + ///< Efficiently disables all accesses guarded by mask + CUTLASS_HOST_DEVICE void clear() + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kCount; ++i) { + predicates[i] = false; + } + } + + ///< CUTLASS_HOST_DEVICE enables all accesses guarded by mask + CUTLASS_DEVICE void enable() + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kCount; ++i) { + predicates[i] = true; + } + } + }; + + /// Shared storage allocation needed by the predicated tile + // iterator for reduction. + struct SharedStorage { + // + // Type definitions + // + using Shape = MatrixShape; + + /// Shape of the shared memory allocation for the reduced values store + using StorageShape = MatrixShape; + + // + // Data members + + // + // Methods + // + AlignedBuffer storage; + + CUTLASS_DEVICE + Element* data() { return storage.data(); } + + SharedStorage() {} + + CUTLASS_DEVICE + void initSmem(EpilogueOpParams const& user_params) + { + Element* shared_elem_arr = data(); + constexpr auto maxVal = std::numeric_limits::max(); + + for (int row = threadIdx.x; row < total_rows; row += blockDim.x) { + user_params.red_op_.init(&shared_elem_arr[row], maxVal); + } + } + }; + + template + struct select_reduce { + /// Performs warp level reduction and stores a reduced output to memory + CUTLASS_DEVICE + select_reduce(OutT value, + ValT prev_red_val, + cg_reduce_op_t reduce_op, + cg_group_t cg_warp_group, + OutT& shmem_ptr) + { + if (cg_warp_group.any(reduce_op.isAmin(value, prev_red_val))) { + OutT reduced_val = cg::reduce(cg_warp_group, value, reduce_op); + if (cg_warp_group.thread_rank() == 0) { shmem_ptr = reduced_val; } + } + } + }; + + template + struct select_reduce> { + using ValT = float; + using Ty = raft::KeyValuePair; + /// Performs warp level reduction of key value pair and stores a reduced output to memory + CUTLASS_DEVICE + select_reduce(Ty val_to_red, + float prev_red_val, + cg_reduce_op_t cg_reduce_op, + cg_group_t cg_warp_group, + Ty& shmem_ptr) + { + ValT val = val_to_red.value; + + if (cg_warp_group.any(cg_reduce_op.isAmin(val, prev_red_val))) { + ValT reduced_val = cg::reduce(cg_warp_group, val, cg_reduce_op); + bool pred = (reduced_val == val); + auto subTile = cg::binary_partition(cg_warp_group, pred); + if (pred) { + if (subTile.thread_rank() == 0) { shmem_ptr = val_to_red; } + } + } + } + }; + + template + struct select_reduce> { + using ValT = double; + using Ty = raft::KeyValuePair; + /// Performs warp level reduction of key value pair and stores a reduced output to memory + CUTLASS_DEVICE + select_reduce(Ty val_to_red, + double prev_red_val, + cg_reduce_op_t cg_reduce_op, + cg_group_t cg_warp_group, + Ty& shmem_ptr) + { + ValT val = val_to_red.value; + + if (cg_warp_group.any(cg_reduce_op.isAmin(val, prev_red_val))) { + ValT reduced_val = cg::reduce(cg_warp_group, val, cg_reduce_op); + bool pred = (reduced_val == val); + auto subTile = cg::binary_partition(cg_warp_group, pred); + if (pred) { + if (subTile.thread_rank() == 0) { shmem_ptr = val_to_red; } + } + } + } + }; + + private: + // + // Data members + // + + /// Parameters structure containing reference and precomputed state. + Params params_; + + /// Byte-level pointer first tile offset of this threadblock. + volatile uint8_t* first_tile_byte_pointer_; + + /// Array of boolean values to contain steady-state predicates + Mask mask_; + + /// Extent of the matrix tile in rows + Index extent_row_; + + /// Extent of the matrix tile in rows + Index extent_column_; + + /// A thread's starting row position (assuming steady-state predicates have been computed) + Index thread_start_row_; + Index block_start_row_first_tile_; + + /// A thread's starting column + Index thread_start_column_; + + /// Internal state counter + int state_[3]; + // mutable int shared_tile_id; + + /// Scatter indices + int const* indices_; + + const int do_gmem_reduction_; + + // + // Static asserts about internal strides + // + + static_assert(sizeof(extent_row_) == 4, "Expected 32b extents"); + static_assert(sizeof(thread_start_row_) == 4, "Expected 32b extents"); + static_assert(sizeof(Params::stride) == 8, "Expected 64b strides"); + + protected: + SharedStorage& shared_storage_; + + private: + // + // Methods + // + public: + // + // Methods + // + /// Constructor + CUTLASS_DEVICE + PredicatedTileIteratorReducedVec(SharedStorage& shared_storage, + Params const& params, + volatile Element* pointer, + TensorCoord extent, + int thread_idx, + const bool do_gmem_reduction, + TensorCoord threadblock_offset = TensorCoord(), + int const* indices = nullptr) + : params_(params), + indices_(indices), + shared_storage_(shared_storage), + do_gmem_reduction_(do_gmem_reduction) + { + TensorCoord thread_offset = ThreadMap::initial_offset(thread_idx) + threadblock_offset; + + extent_row_ = extent.row(); + extent_column_ = extent.column(); + + thread_start_row_ = thread_offset.row(); + thread_start_column_ = thread_offset.column(); + + TensorCoord block_offset = ThreadMap::initial_offset(0) + threadblock_offset; + block_start_row_first_tile_ = block_offset.row(); + + // Initialize predicates + CUTLASS_PRAGMA_UNROLL + for (int c = 0; c < ThreadMap::Iterations::kColumn * kElementsPerAccess; ++c) { + int columnPerAccess = (c / kElementsPerAccess); + int columnWithinPerAccess = c % kElementsPerAccess; + mask_.predicates[c] = ((thread_offset.column() + ThreadMap::Delta::kColumn * columnPerAccess + + columnWithinPerAccess) < extent.column()); + } + + if (threadblock_offset.column() == 0) { + EpilogueOpParams const& user_params = params_.user_param; + shared_storage_.initSmem(user_params); + } + __syncthreads(); + + // Null pointer performs no accesses + if (!pointer) { mask_.clear(); } + + if (ScatterD && !indices) { mask_.clear(); } + + // Initialize pointer + first_tile_byte_pointer_ = reinterpret_cast(pointer) + + LongIndex(block_offset.row()) * LongIndex(params_.stride); + + // Initialize internal state counter + state_[0] = state_[1] = state_[2] = 0; + } + + CUTLASS_DEVICE void dumpToGmem() + { + if (block_start_row_first_tile_ >= extent_row_) { return; } + + if (do_gmem_reduction_) { + EpilogueOpParams const& user_params = params_.user_param; + const uint32_t mutex_id = (block_start_row_first_tile_ / total_rows); + const bool useGmemMutex = (gridDim.x != ((extent_row_ - 1 + total_rows) / total_rows)); + int row = threadIdx.x; + Element* shared_elem_arr = shared_storage_.data(); + Element row_local_min; + if (row < total_rows) { row_local_min = shared_elem_arr[row]; } + + // single lock per block for multiple rows + if (useGmemMutex && threadIdx.x == 0) { user_params.bin_mutex_[mutex_id].acquire(); } + __syncthreads(); + + if (row < total_rows) { + volatile Element* gmem_ptr = reinterpret_cast(first_tile_byte_pointer_); + + if ((block_start_row_first_tile_ + row) < extent_row_) { + user_params.red_op_(block_start_row_first_tile_ + row, (gmem_ptr + row), row_local_min); + } + } + + __syncthreads(); + __threadfence(); + + if (useGmemMutex && (threadIdx.x == 0)) { + // release mutex lock. + user_params.bin_mutex_[mutex_id].release(); + } + shared_storage_.initSmem(user_params); + __syncthreads(); + } + } + + /// Destructor + CUTLASS_DEVICE + ~PredicatedTileIteratorReducedVec() {} + + /// Performs reduction and Stores a reduced output to memory + CUTLASS_DEVICE + void store_with_byte_offset(Fragment& frag, int64_t byte_offset) const + { + AccessTypeValT* frag_ptr = reinterpret_cast(&frag); + + cg::thread_block cta = cg::this_thread_block(); + // tile_width 16 is required if kElementPerAccess > 1 + constexpr int tile_width = (32 / ThreadMap::Delta::kColumn) ? 32 : 16; + cg::thread_block_tile tile32 = cg::tiled_partition(cta); + EpilogueOpParams const& user_params = params_.user_param; + + using cg_reduce_t = decltype(user_params.cg_reduce_op); + using tile32_t = decltype(tile32); + + Element* shared_elem_arr = shared_storage_.data(); + constexpr auto maxVal = std::numeric_limits::max(); + + CUTLASS_PRAGMA_UNROLL + for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { + CUTLASS_PRAGMA_UNROLL + for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { + CUTLASS_PRAGMA_UNROLL + for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { + int frag_row_idx = + (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); + + int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + + cluster * ThreadMap::Delta::kCluster; + + const OutIdxT row_id = row_offset + thread_start_row_; + bool row_guard = (row_id < extent_row_); + + const int frag_idx = frag_row_idx * ThreadMap::Iterations::kColumn * kElementsPerAccess; + Element red_val; + user_params.red_op_.init(&red_val, maxVal); + + if (row_guard) { + CUTLASS_PRAGMA_UNROLL + for (int column = 0; column < ThreadMap::Iterations::kColumn * kElementsPerAccess; + ++column) { + int columnPerAccess = column / kElementsPerAccess; + int columnWithPerAccess = column % kElementsPerAccess; + bool guard = mask_.predicates[column]; + if (guard) { + const OutIdxT key_id = thread_start_column_ + + ThreadMap::Delta::kColumn * columnPerAccess + + columnWithPerAccess; + const int frag_col_idx = frag_idx + column; + + Element this_val; + user_params.red_op_.init(&this_val, (*frag_ptr)[frag_col_idx]); + user_params.red_op_.init_key(this_val, key_id); + user_params.red_op_(row_id, &red_val, this_val); + } + } + } + const int iter_row = (row_id % total_rows); + const auto prev_red_val = user_params.red_op_.get_value(shared_elem_arr[iter_row]); + if (row_guard) { + // select_reduce doesn't need to use `red_op_` as at the warp level we use cg_reduce_op, + // this satisfies the requirement of mst/single linkage of checking colors buffer. + select_reduce red_obj( + red_val, prev_red_val, user_params.cg_reduce_op, tile32, shared_elem_arr[iter_row]); + } + } + } + } + __syncthreads(); + } + + /// Stores a fragment to memory + CUTLASS_DEVICE + void store(Fragment& frag) const { store_with_byte_offset(frag, 0); } + + CUTLASS_DEVICE + MatrixCoord thread_start() const { return MatrixCoord(thread_start_row_, thread_start_column_); } + + /// Need to get the thread start row from the tile iterator + CUTLASS_DEVICE + int32_t thread_start_row() const { return thread_start_row_; } + + /// Need to get the thread start row from the tile iterator + CUTLASS_DEVICE + int32_t thread_start_column() const { return thread_start_column_; } + + /// Extent of the matrix in rows + CUTLASS_DEVICE + Index extent_row() const { return extent_row_; } + + /// Extent of the matrix in columns + CUTLASS_DEVICE + Index extent_column() const { return extent_column_; } + + /// Advances to the next position to load or store + CUTLASS_HOST_DEVICE + PredicatedTileIteratorReducedVec& operator++() + { + ++state_[0]; + + thread_start_row_ += ThreadMap::Shape::kRow; + + if (state_[0] == ThreadMap::Count::kRow) { + state_[0] = 0; + ++state_[1]; + + thread_start_row_ += + (ThreadMap::Shape::kGroup - 1) * ThreadMap::Shape::kRow * ThreadMap::Count::kRow; + + if (state_[1] == ThreadMap::Count::kGroup) { + state_[1] = 0; + ++state_[2]; + + thread_start_row_ += ThreadMap::Count::kGroup * ThreadMap::Shape::kGroup * + ThreadMap::Count::kRow * ThreadMap::Shape::kRow; + + if (state_[2] == ThreadMap::Count::kCluster) { state_[2] = 0; } + } + } + return *this; + } + + ///< Efficiently disables all accesses guarded by mask + CUTLASS_DEVICE void clear_mask() { mask_.clear(); } + + ///< Efficiently enables all accesses guarded by mask + CUTLASS_DEVICE void enable_mask() { mask_.enable(); } + + ///< Sets the mask + CUTLASS_DEVICE void get_mask(Mask& mask) const { mask = mask_; } + + ///< Sets the mask + CUTLASS_DEVICE void set_mask(Mask const& mask) { mask_ = mask; } +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace threadblock +} // namespace epilogue +} // namespace cutlass + +//////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/fused_distance_nn/simt_kernel.cuh b/cpp/src/distance/detail/fused_distance_nn/simt_kernel.cuh new file mode 100644 index 0000000000..184063c8ba --- /dev/null +++ b/cpp/src/distance/detail/fused_distance_nn/simt_kernel.cuh @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2021-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../distance_ops/l2_exp.cuh" // ops::l2_exp_distance_op +#include "../pairwise_distance_base.cuh" // PairwiseDistances +#include // raft::KeyValuePair +#include // Policy + +#include // size_t +#include // std::numeric_limits + +namespace cuvs { +namespace distance { +namespace detail { + +// TODO: specialize this function for MinAndDistanceReduceOp +// with atomicCAS of 64 bit which will eliminate mutex and shfls +template +DI void updateReducedVal( + int* mutex, OutT* min, KVPair* val, ReduceOpT red_op, IdxT m, IdxT gridStrideY) +{ + const auto lid = threadIdx.x % raft::WarpSize; + const auto accrowid = threadIdx.x / P::AccThCols; + + // Update each output row in order within a warp. This will resolve hang + // issues with pre-Volta architectures +#pragma unroll + for (int j = 0; j < (raft::WarpSize / P::AccThCols); j++) { + if (lid == j * P::AccThCols) { +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + auto rid = gridStrideY + accrowid + i * P::AccThRows; + if (rid < m) { + auto value = val[i]; + while (atomicCAS(mutex + rid, 0, 1) == 1) + ; + __threadfence(); + red_op(rid, min + rid, value); + __threadfence(); + atomicCAS(mutex + rid, 1, 0); + } + } + } + } +} + +template +__launch_bounds__(P::Nthreads, 2) RAFT_KERNEL fusedDistanceNNkernel(OutT* min, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + DataT maxVal, + int* mutex, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + OpT distance_op, + FinalLambda fin_op) +{ +// compile only if below non-ampere arch. +#if __CUDA_ARCH__ < 800 + extern __shared__ char smem[]; + + typedef raft::KeyValuePair KVPair; + KVPair val[P::AccRowsPerTh]; +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + val[i] = {0, maxVal}; + } + + // epilogue operation lambda for final value calculation + auto epilog_lambda = [n, pairRedOp, &val, maxVal] __device__( + DataT acc[P::AccRowsPerTh][P::AccColsPerTh], + DataT * regxn, + DataT * regyn, + IdxT gridStrideX, + IdxT gridStrideY) { + KVPReduceOpT pairRed_op(pairRedOp); + + // intra thread reduce + const auto acccolid = threadIdx.x % P::AccThCols; + const auto accrowid = threadIdx.x / P::AccThCols; +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + auto tmpkey = acccolid + j * P::AccThCols + gridStrideX; + KVPair tmp = {tmpkey, acc[i][j]}; + if (tmpkey < n) { + val[i] = pairRed_op(accrowid + i * P::AccThRows + gridStrideY, tmp, val[i]); + } + } + } + }; + + auto rowEpilog_lambda = + [m, mutex, min, pairRedOp, redOp, &val, maxVal] __device__(IdxT gridStrideY) { + KVPReduceOpT pairRed_op(pairRedOp); + ReduceOpT red_op(redOp); + + const auto accrowid = threadIdx.x / P::AccThCols; + const auto lid = raft::laneId(); + + // reduce +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = P::AccThCols / 2; j > 0; j >>= 1) { + // Actually, the srcLane (lid +j) should be (lid +j) % P:AccThCols, + // but the shfl op applies the modulo internally. + auto tmpkey = raft::shfl(val[i].key, lid + j, P::AccThCols); + auto tmpvalue = raft::shfl(val[i].value, lid + j, P::AccThCols); + KVPair tmp = {tmpkey, tmpvalue}; + val[i] = pairRed_op(accrowid + i * P::AccThRows + gridStrideY, tmp, val[i]); + } + } + + updateReducedVal(mutex, min, val, red_op, m, gridStrideY); + + // reset the val array. +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + val[i] = {0, maxVal}; + } + }; + + IdxT lda = k, ldb = k, ldd = n; + constexpr bool row_major = true; + constexpr bool write_out = false; + PairwiseDistances + obj(x, + y, + m, + n, + k, + lda, + ldb, + ldd, + xn, + yn, + nullptr, // Output pointer + smem, + distance_op, + epilog_lambda, + fin_op, + rowEpilog_lambda); + obj.run(); +#endif +} + +} // namespace detail +} // namespace distance +} // namespace cuvs diff --git a/cpp/src/distance/detail/fused_l2_nn.cuh b/cpp/src/distance/detail/fused_l2_nn.cuh new file mode 100644 index 0000000000..3b8c426ea7 --- /dev/null +++ b/cpp/src/distance/detail/fused_l2_nn.cuh @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2021-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../distance_ops/l2_exp.cuh" // ops::l2_exp_distance_op +#include "../fused_distance_nn/cutlass_base.cuh" +#include "../pairwise_distance_base.cuh" // PairwiseDistances +#include // raft::KeyValuePair +#include // raft::identity_op +#include // Policy +#include // raft::util::arch::SM_* +#include // raft::ceildiv, raft::shfl + +#include // size_t +#include // std::numeric_limits + +namespace cuvs { +namespace distance { + +namespace detail { + +template +struct KVPMinReduceImpl { + typedef raft::KeyValuePair KVP; + DI KVP operator()(LabelT rit, const KVP& a, const KVP& b) { return b.value < a.value ? b : a; } + DI KVP operator()(const KVP& a, const KVP& b) { return b.value < a.value ? b : a; } + +}; // KVPMinReduce + +template +struct MinAndDistanceReduceOpImpl { + typedef typename raft::KeyValuePair KVP; + DI void operator()(LabelT rid, KVP* out, const KVP& other) const + { + if (other.value < out->value) { + out->key = other.key; + out->value = other.value; + } + } + + DI void operator()(LabelT rid, DataT* out, const KVP& other) const + { + if (other.value < *out) { *out = other.value; } + } + + DI void operator()(LabelT rid, DataT* out, const DataT& other) const + { + if (other < *out) { *out = other; } + } + + DI void init(DataT* out, DataT maxVal) const { *out = maxVal; } + DI void init(KVP* out, DataT maxVal) const { out->value = maxVal; } + + DI void init_key(DataT& out, LabelT idx) const { return; } + DI void init_key(KVP& out, LabelT idx) const { out.key = idx; } + + DI DataT get_value(KVP& out) const + { + return out.value; + ; + } + DI DataT get_value(DataT& out) const { return out; } +}; + +template +struct MinReduceOpImpl { + typedef typename raft::KeyValuePair KVP; + DI void operator()(LabelT rid, DataT* out, const KVP& other) + { + if (other.value < *out) { *out = other.value; } + } + + DI void init(DataT* out, DataT maxVal) { *out = maxVal; } +}; + +template +RAFT_KERNEL initKernel(OutT* min, IdxT m, DataT maxVal, ReduceOpT redOp) +{ + auto tid = IdxT(blockIdx.x) * blockDim.x + threadIdx.x; + if (tid < m) { redOp.init(min + tid, maxVal); } +} + +template +void initialize(OutT* min, IdxT m, DataT maxVal, ReduceOpT redOp, cudaStream_t stream) +{ + auto blks = raft::ceildiv(m, 256); + initKernel<<>>(min, m, maxVal, redOp); +} + +// TODO: specialize this function for MinAndDistanceReduceOp +// with atomicCAS of 64 bit which will eliminate mutex and shfls +template +DI void updateReducedVal( + int* mutex, OutT* min, KVPair* val, ReduceOpT red_op, IdxT m, IdxT gridStrideY) +{ + const auto lid = threadIdx.x % raft::WarpSize; + const auto accrowid = threadIdx.x / P::AccThCols; + + // Update each output row in order within a warp. This will resolve hang + // issues with pre-Volta architectures +#pragma unroll + for (int j = 0; j < (raft::WarpSize / P::AccThCols); j++) { + if (lid == j * P::AccThCols) { +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + auto rid = gridStrideY + accrowid + i * P::AccThRows; + if (rid < m) { + auto value = val[i]; + while (atomicCAS(mutex + rid, 0, 1) == 1) + ; + __threadfence(); + red_op(rid, min + rid, value); + __threadfence(); + atomicCAS(mutex + rid, 1, 0); + } + } + } + } +} + +template +__launch_bounds__(P::Nthreads, 2) RAFT_KERNEL fusedL2NNkernel(OutT* min, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + DataT maxVal, + int* mutex, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + OpT distance_op, + FinalLambda fin_op) +{ +// compile only if below non-ampere arch. +#if __CUDA_ARCH__ < 800 + extern __shared__ char smem[]; + + typedef raft::KeyValuePair KVPair; + KVPair val[P::AccRowsPerTh]; +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + val[i] = {0, maxVal}; + } + + // epilogue operation lambda for final value calculation + auto epilog_lambda = [n, pairRedOp, &val, maxVal] __device__( + DataT acc[P::AccRowsPerTh][P::AccColsPerTh], + DataT * regxn, + DataT * regyn, + IdxT gridStrideX, + IdxT gridStrideY) { + KVPReduceOpT pairRed_op(pairRedOp); + + // intra thread reduce + const auto acccolid = threadIdx.x % P::AccThCols; + const auto accrowid = threadIdx.x / P::AccThCols; +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + auto tmpkey = acccolid + j * P::AccThCols + gridStrideX; + KVPair tmp = {tmpkey, acc[i][j]}; + if (tmpkey < n) { + val[i] = pairRed_op(accrowid + i * P::AccThRows + gridStrideY, tmp, val[i]); + } + } + } + }; + + auto rowEpilog_lambda = + [m, mutex, min, pairRedOp, redOp, &val, maxVal] __device__(IdxT gridStrideY) { + KVPReduceOpT pairRed_op(pairRedOp); + ReduceOpT red_op(redOp); + + const auto accrowid = threadIdx.x / P::AccThCols; + const auto lid = raft::laneId(); + + // reduce +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = P::AccThCols / 2; j > 0; j >>= 1) { + // Actually, the srcLane (lid +j) should be (lid +j) % P:AccThCols, + // but the shfl op applies the modulo internally. + auto tmpkey = raft::shfl(val[i].key, lid + j, P::AccThCols); + auto tmpvalue = raft::shfl(val[i].value, lid + j, P::AccThCols); + KVPair tmp = {tmpkey, tmpvalue}; + val[i] = pairRed_op(accrowid + i * P::AccThRows + gridStrideY, tmp, val[i]); + } + } + + updateReducedVal(mutex, min, val, red_op, m, gridStrideY); + + // reset the val array. +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + val[i] = {0, maxVal}; + } + }; + + IdxT lda = k, ldb = k, ldd = n; + constexpr bool row_major = true; + constexpr bool write_out = false; + PairwiseDistances + obj(x, + y, + m, + n, + k, + lda, + ldb, + ldd, + xn, + yn, + nullptr, // Output pointer + smem, + distance_op, + epilog_lambda, + fin_op, + rowEpilog_lambda); + obj.run(); +#endif +} + +// cg::reduce functor for FusedDistanceNN used in its cutlass version +// to output the min distance value & key(loc id). +// This is used in fused_distance_nn/predicated_tile_iterator_reduced_vec.h +// store_with_byte_offset() passed to cg::reduce() & select_reduce. +template +struct kvp_cg_min_reduce_op { + typedef typename raft::KeyValuePair KVP; + + __host__ __device__ kvp_cg_min_reduce_op() noexcept {}; + + using AccTypeT = AccType; + using IndexT = Index; + // functor signature. + __host__ __device__ KVP operator()(KVP a, KVP b) const { return a.value < b.value ? a : b; } + + __host__ __device__ AccType operator()(AccType a, AccType b) const { return min(a, b); } + + __host__ __device__ bool isAmin(AccType a, AccType b) const { return a < b ? true : false; } +}; + +template +void fusedL2NNImpl(OutT* min, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + int* workspace, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + bool sqrt, + bool initOutBuffer, + cudaStream_t stream) +{ + // The kernel policy is determined by fusedL2NN. + typedef Policy P; + + dim3 blk(P::Nthreads); + auto nblks = raft::ceildiv(m, P::Nthreads); + constexpr auto maxVal = std::numeric_limits::max(); + typedef raft::KeyValuePair KVPair; + + RAFT_CUDA_TRY(cudaMemsetAsync(workspace, 0, sizeof(int) * m, stream)); + if (initOutBuffer) { + initKernel + <<>>(min, m, maxVal, redOp); + RAFT_CUDA_TRY(cudaGetLastError()); + } + + namespace arch = raft::util::arch; + using AccT = DataT; + ops::l2_exp_distance_op distance_op{sqrt}; + + raft::identity_op fin_op{}; + + auto kernel = fusedL2NNkernel; + + // Get pointer to fp32 SIMT kernel to determine the best compute architecture + // out of all for which the kernel was compiled for that matches closely + // to the current device. Other methods to determine the architecture (that do not + // require a pointer) can be error prone. See: + // https://github.com/NVIDIA/cub/issues/545 + void* kernel_ptr = reinterpret_cast(kernel); + auto runtime_arch = arch::kernel_virtual_arch(kernel_ptr); + auto cutlass_range = arch::SM_range(arch::SM_80(), arch::SM_future()); + + if (cutlass_range.contains(runtime_arch)) { + // If device is SM_80 or later, use CUTLASS-based kernel. + using L2Op = cuvs::distance::detail::ops::l2_exp_cutlass_op; + using kvp_cg_min_reduce_op_ = kvp_cg_min_reduce_op; + kvp_cg_min_reduce_op_ cg_reduce_op; + L2Op L2_dist_op(sqrt); + + IdxT lda, ldb, ldd; + lda = k, ldb = k, ldd = n; + + cutlassFusedDistanceNN(x, + y, + xn, + yn, + m, + n, + k, + lda, + ldb, + ldd, + min, + workspace, + cg_reduce_op, + L2_dist_op, + redOp, + pairRedOp, + stream); + } else { + // If device less than SM_80, use fp32 SIMT kernel. + constexpr size_t shmemSize = P::SmemSize + ((P::Mblk + P::Nblk) * sizeof(DataT)); + dim3 grid = launchConfigGenerator

(m, n, shmemSize, kernel); + + kernel<<>>( + min, x, y, xn, yn, m, n, k, maxVal, workspace, redOp, pairRedOp, distance_op, fin_op); + RAFT_CUDA_TRY(cudaGetLastError()); + } +} + +} // namespace detail +} // namespace distance +} // namespace cuvs diff --git a/cpp/src/distance/detail/kernels/gram_matrix.cuh b/cpp/src/distance/detail/kernels/gram_matrix.cuh new file mode 100644 index 0000000000..009941fa7b --- /dev/null +++ b/cpp/src/distance/detail/kernels/gram_matrix.cuh @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2022-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../../distance.cuh" +#include +#include +#include +#include +// #include +#include +#include +#include +#include + +namespace cuvs::distance::kernels::detail { + +template +using dense_input_matrix_view_t = raft::device_matrix_view; +template +using dense_output_matrix_view_t = raft::device_matrix_view; +template +using csr_input_matrix_view_t = raft::device_csr_matrix_view; + +/** + * Base class for general Gram matrices + * A Gram matrix is the Hermitian matrix of inner probucts G_ik = + * Here, the inner product is evaluated for all elements from vectors sets X1, + * and X2. + * + * To be more precise, on exit the output buffer will store: + * - if is_row_major == true: out[j+k*n1] = , + * - if is_row_major == false: out[j*n2 + k] = , + * where x1_j is the j-th vector from the x1 set and x2_k is the k-th vector + * from the x2 set. + */ +template +class GramMatrixBase { + protected: + cublasHandle_t cublas_handle; + bool legacy_interface; + + public: + GramMatrixBase() : legacy_interface(false){}; + [[deprecated]] GramMatrixBase(cublasHandle_t cublas_handle) + : cublas_handle(cublas_handle), legacy_interface(true){}; + + virtual ~GramMatrixBase(){}; + + /** Convenience function to evaluate the Gram matrix for two vector sets. + * Vector sets are provided in Matrix format + * + * @param [in] handle raft handle + * @param [in] x1 dense device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 optional L2-norm of x1's rows for computation within RBF. + * @param norm_x2 optional L2-norm of x2's rows for computation within RBF. + */ + void operator()(raft::resources const& handle, + dense_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1 = nullptr, + math_t* norm_x2 = nullptr) + { + evaluate(handle, x1, x2, out, norm_x1, norm_x2); + } + + /** Convenience function to evaluate the Gram matrix for two vector sets. + * Vector sets are provided in Matrix format + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 optional L2-norm of x1's rows for computation within RBF. + * @param norm_x2 optional L2-norm of x2's rows for computation within RBF. + */ + void operator()(raft::resources const& handle, + csr_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1 = nullptr, + math_t* norm_x2 = nullptr) + { + evaluate(handle, x1, x2, out, norm_x1, norm_x2); + } + + /** Convenience function to evaluate the Gram matrix for two vector sets. + * Vector sets are provided in Matrix format + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 csr device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 optional L2-norm of x1's rows for computation within RBF. + * @param norm_x2 optional L2-norm of x2's rows for computation within RBF. + */ + void operator()(raft::resources const& handle, + csr_input_matrix_view_t x1, + csr_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1 = nullptr, + math_t* norm_x2 = nullptr) + { + evaluate(handle, x1, x2, out, norm_x1, norm_x2); + } + + // unfortunately, 'evaluate' cannot be templatized as it needs to be virtual + + /** Evaluate the Gram matrix for two vector sets using simple dot product. + * + * @param [in] handle raft handle + * @param [in] x1 dense device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + virtual void evaluate(raft::resources const& handle, + dense_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + linear(handle, x1, x2, out); + } + /** Evaluate the Gram matrix for two vector sets using simple dot product. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + virtual void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + linear(handle, x1, x2, out); + } + /** Evaluate the Gram matrix for two vector sets using simple dot product. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 csr device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + virtual void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + csr_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + linear(handle, x1, x2, out); + } + + /** Evaluate the Gram matrix for two vector sets using simple dot product. + * + * @param [in] x1 device array of vectors, size [n1*n_cols] + * @param [in] n1 number vectors in x1 + * @param [in] n_cols number of columns (features) in x1 and x2 + * @param [in] x2 device array of vectors, size [n2*n_cols] + * @param [in] n2 number vectors in x2 + * @param [out] out device buffer to store the Gram matrix, size [n1*n2] + * @param [in] is_row_major whether the input and output matrices are in row + * major format + * @param [in] stream cuda stream + * @param ld1 leading dimension of x1 (usually it is n1) + * @param ld2 leading dimension of x2 (usually it is n2) + * @param ld_out leading dimension of out (usually it is n1) + */ + [[deprecated]] virtual void evaluate(const math_t* x1, + int n1, + int n_cols, + const math_t* x2, + int n2, + math_t* out, + bool is_row_major, + cudaStream_t stream, + int ld1, + int ld2, + int ld_out) + { + linear(x1, n1, n_cols, x2, n2, out, is_row_major, stream, ld1, ld2, ld_out); + } + + /** Convenience function to evaluate the Gram matrix for two vector sets. + * + * @param [in] x1 device array of vectors, size [n1*n_cols] + * @param [in] n1 number vectors in x1 + * @param [in] n_cols number of columns (features) in x1 and x2 + * @param [in] x2 device array of vectors, size [n2*n_cols] + * @param [in] n2 number vectors in x2 + * @param [out] out device buffer to store the Gram matrix, size [n1*n2] + * @param [in] is_row_major whether the input and output matrices are in row + * major format + * @param [in] stream cuda stream + * @param ld1 leading dimension of x1 + * @param ld2 leading dimension of x2 + * @param ld_out leading dimension of out + */ + [[deprecated]] void operator()(const math_t* x1, + int n1, + int n_cols, + const math_t* x2, + int n2, + math_t* out, + bool is_row_major, + cudaStream_t stream, + int ld1 = 0, + int ld2 = 0, + int ld_out = 0) + { + ASSERT(legacy_interface, "Legacy interface can only be used with legacy ctor."); + if (ld1 <= 0) { ld1 = is_row_major ? n_cols : n1; } + if (ld2 <= 0) { ld2 = is_row_major ? n_cols : n2; } + if (ld_out <= 0) { ld_out = is_row_major ? n2 : n1; } + evaluate(x1, n1, n_cols, x2, n2, out, is_row_major, stream, ld1, ld2, ld_out); + } + + protected: + /** Calculates the Gram matrix using simple dot product between vector sets. + * + * out = x1 * x2 + * + * Can be used as a building block for more complex kernel functions. + * + * @param [in] x1 device array of vectors, size [n1*n_cols] + * @param [in] n1 number vectors in x1 + * @param [in] n_cols number of columns (features) in x1 and x2 + * @param [in] x2 device array of vectors, size [n2*n_cols] + * @param [in] n2 number vectors in x2 + * @param [out] out device buffer to store the Gram matrix, size [n1*n2] + * @param [in] is_row_major whether the input and output matrices are in row + * major format + * @param [in] stream cuda stream + * @param ld1 leading dimension of x1 + * @param ld2 leading dimension of x2 + * @param ld_out leading dimension of out + */ + [[deprecated]] void linear(const math_t* x1, + int n1, + int n_cols, + const math_t* x2, + int n2, + math_t* out, + bool is_row_major, + cudaStream_t stream, + int ld1, + int ld2, + int ld_out) + { + math_t alpha = 1.0; + math_t beta = 0.0; + if (is_row_major) { + // #TODO: Call from public API when ready + RAFT_CUBLAS_TRY(raft::linalg::detail::cublasgemm(cublas_handle, + CUBLAS_OP_T, + CUBLAS_OP_N, + n2, + n1, + n_cols, + &alpha, + x2, + ld2, + x1, + ld1, + &beta, + out, + ld_out, + stream)); + } else { + // #TODO: Call from public API when ready + RAFT_CUBLAS_TRY(raft::linalg::detail::cublasgemm(cublas_handle, + CUBLAS_OP_N, + CUBLAS_OP_T, + n1, + n2, + n_cols, + &alpha, + x1, + ld1, + x2, + ld2, + &beta, + out, + ld_out, + stream)); + } + } + + protected: + bool get_is_row_major(dense_output_matrix_view_t matrix) + { + return (matrix.stride(1) == 1); + } + + bool get_is_row_major(dense_input_matrix_view_t matrix) + { + return (matrix.stride(1) == 1); + } + + bool get_is_col_major(dense_output_matrix_view_t matrix) + { + return (matrix.stride(0) == 1); + } + + bool get_is_col_major(dense_input_matrix_view_t matrix) + { + return (matrix.stride(0) == 1); + } + + /** Calculates the Gram matrix using simple dot product between vector sets. + * + * out = x1 * x2 + * + * Can be used as a building block for more complex kernel functions. + * + * @param [in] handle raft handle + * @param [in] x1 dense device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + */ + void linear(raft::resources const& handle, + dense_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out) + { + // check is_row_major consistency + bool is_row_major = get_is_row_major(x1) && get_is_row_major(x2) && get_is_row_major(out); + bool is_col_major = get_is_col_major(x1) && get_is_col_major(x2) && get_is_col_major(out); + ASSERT(is_row_major || is_col_major, + "GramMatrix leading dimensions for x1, x2 and out do not match"); + + // check dimensions + int n1 = out.extent(0); + int n2 = out.extent(1); + int n_cols = x1.extent(1); + ASSERT(x1.extent(0) == n1, "GramMatrix input matrix dimensions for x1 and out do not match"); + ASSERT(x2.extent(0) == n2, "GramMatrix input matrix dimensions for x2 and out do not match"); + ASSERT(x2.extent(1) == n_cols, "GramMatrix input matrix dimensions for x1 and x2 do not match"); + + // extract major stride + int ld1 = is_row_major ? x1.stride(0) : x1.stride(1); + int ld2 = is_row_major ? x2.stride(0) : x2.stride(1); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + + math_t alpha = 1.0; + math_t beta = 0.0; + if (is_row_major) { + // #TODO: Use mdspan-based API when stride-capable + // https://github.com/rapidsai/raft/issues/875 + raft::linalg::gemm(handle, + true, + false, + n2, + n1, + n_cols, + &alpha, + x2.data_handle(), + ld2, + x1.data_handle(), + ld1, + &beta, + out.data_handle(), + ld_out, + resource::get_cuda_stream(handle)); + } else { + // #TODO: Use mdspan-based API when stride-capable + // https://github.com/rapidsai/raft/issues/875 + raft::linalg::gemm(handle, + false, + true, + n1, + n2, + n_cols, + &alpha, + x1.data_handle(), + ld1, + x2.data_handle(), + ld2, + &beta, + out.data_handle(), + ld_out, + resource::get_cuda_stream(handle)); + } + } + + /** Calculates the Gram matrix using simple dot product between vector sets. + * + * out = x1 * x2 + * + * Can be used as a building block for more complex kernel functions. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + */ + void linear(raft::resources const& handle, + csr_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out) + { + // check is_row_major consistency + bool is_row_major = get_is_row_major(x2) && get_is_row_major(out); + bool is_col_major = get_is_col_major(x2) && get_is_col_major(out); + ASSERT(is_row_major || is_col_major, + "GramMatrix leading dimensions for x2 and out do not match"); + + // check dimensions + auto x1_structure = x1.structure_view(); + ASSERT(x1_structure.get_n_rows() == out.extent(0), + "GramMatrix input matrix dimensions for x1 and out do not match"); + ASSERT(x2.extent(0) == out.extent(1), + "GramMatrix input matrix dimensions for x2 and out do not match"); + ASSERT(x2.extent(1) == x1_structure.get_n_cols(), + "GramMatrix input matrix dimensions for x1 and x2 do not match"); + + math_t alpha = 1.0; + math_t beta = 0.0; + + raft::sparse::linalg::spmm(handle, false, true, &alpha, x1, x2, &beta, out); + } + + /** Calculates the Gram matrix using simple dot product between vector sets. + * + * out = x1 * x2 + * + * Can be used as a building block for more complex kernel functions. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 csr device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + */ + void linear(raft::resources const& handle, + csr_input_matrix_view_t x1, + csr_input_matrix_view_t x2, + dense_output_matrix_view_t out) + { + // check layout consistency (w.r.t. strides a matrix might be both row & col major) + bool is_row_major_nopad = get_is_row_major(out) && out.stride(0) == out.extent(1); + bool is_col_major_nopad = get_is_col_major(out) && out.stride(1) == out.extent(0); + + ASSERT(is_row_major_nopad || is_col_major_nopad, + "Sparse linear Kernel distance does not support ld_out parameter"); + + // switch a,b based on is_row_major + if (is_col_major_nopad) { + auto out_row_major = raft::make_device_matrix_view( + out.data_handle(), out.extent(1), out.extent(0)); + raft::sparse::distance::pairwise_distance( + handle, x2, x1, out_row_major, cuvs::distance::DistanceType::InnerProduct, 0.0); + } else { + auto out_row_major = raft::make_device_matrix_view( + out.data_handle(), out.extent(0), out.extent(1)); + raft::sparse::distance::pairwise_distance( + handle, x1, x2, out_row_major, cuvs::distance::DistanceType::InnerProduct, 0.0); + } + } +}; + +}; // end namespace cuvs::distance::kernels::detail diff --git a/cpp/src/distance/detail/kernels/kernel_factory.cuh b/cpp/src/distance/detail/kernels/kernel_factory.cuh new file mode 100644 index 0000000000..534339a153 --- /dev/null +++ b/cpp/src/distance/detail/kernels/kernel_factory.cuh @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "gram_matrix.cuh" +#include "kernel_matrices.cuh" + +#include +#include + +namespace cuvs::distance::kernels::detail { + +template +class KernelFactory { + public: + static GramMatrixBase* create(KernelParams params) + { + GramMatrixBase* res; + // KernelParams is not templated, we convert the parameters to math_t here: + math_t coef0 = params.coef0; + math_t gamma = params.gamma; + switch (params.kernel) { + case LINEAR: res = new GramMatrixBase(); break; + case POLYNOMIAL: res = new PolynomialKernel(params.degree, gamma, coef0); break; + case TANH: res = new TanhKernel(gamma, coef0); break; + case RBF: res = new RBFKernel(gamma); break; + default: throw raft::exception("Kernel not implemented"); + } + return res; + } + + [[deprecated]] static GramMatrixBase* create(KernelParams params, cublasHandle_t handle) + { + GramMatrixBase* res; + // KernelParams is not templated, we convert the parameters to math_t here: + math_t coef0 = params.coef0; + math_t gamma = params.gamma; + switch (params.kernel) { + case LINEAR: res = new GramMatrixBase(handle); break; + case POLYNOMIAL: + res = new PolynomialKernel(params.degree, gamma, coef0, handle); + break; + case TANH: res = new TanhKernel(gamma, coef0, handle); break; + case RBF: res = new RBFKernel(gamma, handle); break; + default: throw raft::exception("Kernel not implemented"); + } + return res; + } +}; + +}; // end namespace cuvs::distance::kernels::detail diff --git a/cpp/src/distance/detail/kernels/kernel_matrices.cuh b/cpp/src/distance/detail/kernels/kernel_matrices.cuh new file mode 100644 index 0000000000..bff5bda923 --- /dev/null +++ b/cpp/src/distance/detail/kernels/kernel_matrices.cuh @@ -0,0 +1,777 @@ +/* + * Copyright (c) 2019-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "gram_matrix.cuh" + +#include "../detail/kernels/rbf_fin_op.cuh" +#include +#include +#include +#include +#include + +namespace cuvs::distance::kernels::detail { + +/** Epiloge function for polynomial kernel without padding. + * Calculates output = (gain*in + offset)^exponent + * @param inout device vector in column major format, size [len] + * @param len array length + * @param exponent + * @param gain + * @param offset + */ +template +RAFT_KERNEL polynomial_kernel_nopad( + math_t* inout, size_t len, exp_t exponent, math_t gain, math_t offset) +{ + for (size_t tid = threadIdx.x + blockIdx.x * blockDim.x; tid < len; + tid += blockDim.x * gridDim.x) { + inout[tid] = pow(gain * inout[tid] + offset, exponent); + } +} + +/** Epiloge function for polynomial kernel with padding. + * Calculates output = (gain*input + offset)^exponent + * @param inout device vector in column major format, size [ld * cols] + * @param ld leading dimension of the inout buffer + * @param rows number of rows (rows <= ld) + * @param cols number of columns + * @param exponent + * @param gain + * @param offset + */ +template +RAFT_KERNEL polynomial_kernel( + math_t* inout, int ld, int rows, int cols, exp_t exponent, math_t gain, math_t offset) +{ + for (size_t tidy = threadIdx.y + blockIdx.y * blockDim.y; tidy < cols; + tidy += blockDim.y * gridDim.y) + for (size_t tidx = threadIdx.x + blockIdx.x * blockDim.x; tidx < rows; + tidx += blockDim.x * gridDim.x) { + inout[tidx + tidy * ld] = pow(gain * inout[tidx + tidy * ld] + offset, exponent); + } +} + +/** Epiloge function for tanh kernel without padding. + * Calculates output = tanh(gain*input + offset) + * @param inout device vector, size [len] + * @param len length of the input vector + * @param gain + * @param offset + */ +template +RAFT_KERNEL tanh_kernel_nopad(math_t* inout, size_t len, math_t gain, math_t offset) +{ + for (size_t tid = threadIdx.x + blockIdx.x * blockDim.x; tid < len; + tid += blockDim.x * gridDim.x) { + inout[tid] = tanh(gain * inout[tid] + offset); + } +} + +/** Epiloge function for tanh kernel without padding. + * Calculates output = tanh(gain*input + offset) + * @param inout device vector in column major format, size [ld * cols] + * @param ld leading dimension of the inout buffer + * @param rows number of rows (rows <= ld) + * @param cols number of columns + * @param gain + * @param offset + */ +template +RAFT_KERNEL tanh_kernel(math_t* inout, int ld, int rows, int cols, math_t gain, math_t offset) +{ + for (size_t tidy = threadIdx.y + blockIdx.y * blockDim.y; tidy < cols; + tidy += blockDim.y * gridDim.y) + for (size_t tidx = threadIdx.x + blockIdx.x * blockDim.x; tidx < rows; + tidx += blockDim.x * gridDim.x) { + inout[tidx + tidy * ld] = tanh(gain * inout[tidx + tidy * ld] + offset); + } +} + +/** Epiloge function for rbf kernel using expansion. + * + * Calculates output_ij = exp(-gain * (norm_x_i + norm_y_j - 2*input_ij)); + * + * Intended usage + * - input is the product of two matrices X and Y input_ij = sum_k X_ik * Y_jk + * - norm_x_i = l2_norm(x_i), where x_i is the i-th row of matrix X + * - norm_y_j = l2_norm(y_j), where y_j is the j-th row of matrix Y + * + * @param inout device vector in column major format, size [ld * cols] + * @param ld leading dimension of the inout buffer + * @param rows number of rows (rows <= ld) + * @param cols number of columns + * @param norm_x l2-norm of X's rows + * @param norm_y l2-norm of Y's rows + * @param gain + */ +template +RAFT_KERNEL rbf_kernel_expanded( + math_t* inout, int ld, int rows, int cols, math_t* norm_x, math_t* norm_y, math_t gain) +{ + for (size_t tidy = threadIdx.y + blockIdx.y * blockDim.y; tidy < cols; + tidy += blockDim.y * gridDim.y) { + math_t norm_y_val = norm_y[tidy]; + for (size_t tidx = threadIdx.x + blockIdx.x * blockDim.x; tidx < rows; + tidx += blockDim.x * gridDim.x) { + inout[tidx + tidy * ld] = + exp(-1.0 * gain * (norm_x[tidx] + norm_y_val - inout[tidx + tidy * ld] * 2)); + } + } +} + +namespace { +std::tuple generateLaunchConfig2dElementwiseOp(int n1, int n2) +{ + dim3 block_shape = dim3(32, 4); + const int num_blocks_x = raft::ceildiv(n1, 32); + const int num_blocks_y = std::min(raft::ceildiv(n2, 32), (1 << 16) - 1); + dim3 grid_shape = dim3(num_blocks_x, num_blocks_y); + return std::make_tuple(grid_shape, block_shape); +} +} // namespace + +/** + * Create a kernel matrix using polynomial kernel function. + */ +template +class PolynomialKernel : public GramMatrixBase { + exp_t exponent; + math_t gain; + math_t offset; + + void applyKernel( + math_t* inout, int ld, int rows, int cols, bool is_row_major, cudaStream_t stream) + { + const int n_minor = is_row_major ? cols : rows; + if (ld == n_minor) { + polynomial_kernel_nopad<<((size_t)rows * cols, 128), 128, 0, stream>>>( + inout, rows * cols, exponent, gain, offset); + } else { + int n1 = is_row_major ? cols : rows; + int n2 = is_row_major ? rows : cols; + auto [grid_shape, block_shape] = generateLaunchConfig2dElementwiseOp(n1, n2); + polynomial_kernel<<>>( + inout, ld, n1, n2, exponent, gain, offset); + } + RAFT_CUDA_TRY(cudaPeekAtLastError()); + } + + public: + /** + * Constructs a polynomial kernel object. + * It evaluates the kernel matrix using the following formula: + * K_ij = (gain* + offset)^exponent + * + * @tparam math_t floating point type + * @tparam exp_t type of exponent + * @param exponent + * @param gain + * @param offset + */ + PolynomialKernel(exp_t exponent, math_t gain, math_t offset) + : GramMatrixBase(), exponent(exponent), gain(gain), offset(offset) + { + } + + [[deprecated]] PolynomialKernel(exp_t exponent, math_t gain, math_t offset, cublasHandle_t handle) + : GramMatrixBase(handle), exponent(exponent), gain(gain), offset(offset) + { + } + + /** Evaluate kernel matrix using polynomial kernel. + * + * output[i,k] = (gain* + offset)^exponent, + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and < , > denotes dot product. + * + * @param [in] handle raft handle + * @param [in] x1 dense device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + void evaluate(raft::resources const& handle, + dense_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate kernel matrix using polynomial kernel. + * + * output[i,k] = (gain* + offset)^exponent, + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and < , > denotes dot product. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate kernel matrix using polynomial kernel. + * + * output[i,k] = (gain* + offset)^exponent, + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and < , > denotes dot product. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 csr device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + csr_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate the Gram matrix using the legacy interface. + * + * @param [in] x1 device array of vectors, size [n1*n_cols] + * @param [in] n1 number vectors in x1 + * @param [in] n_cols number of columns (features) in x1 and x2 + * @param [in] x2 device array of vectors, size [n2*n_cols] + * @param [in] n2 number vectors in x2 + * @param [out] out device buffer to store the Gram matrix, size [n1*n2] + * @param [in] is_row_major whether the input and output matrices are in row + * major format + * @param [in] stream cuda stream + * @param ld1 leading dimension of x1 (usually it is n1) + * @param ld2 leading dimension of x2 (usually it is n2) + * @param ld_out leading dimension of out (usually it is n1) + */ + [[deprecated]] void evaluate(const math_t* x1, + int n1, + int n_cols, + const math_t* x2, + int n2, + math_t* out, + bool is_row_major, + cudaStream_t stream, + int ld1, + int ld2, + int ld_out) + { + ASSERT(GramMatrixBase::legacy_interface, + "Legacy interface can only be used with legacy ctor."); + GramMatrixBase::linear( + x1, n1, n_cols, x2, n2, out, is_row_major, stream, ld1, ld2, ld_out); + applyKernel(out, ld_out, n1, n2, is_row_major, stream); + } +}; + +/** + * Create a kernel matrix using tanh kernel function. + */ +template +class TanhKernel : public GramMatrixBase { + math_t gain, offset; + + void applyKernel( + math_t* inout, int ld, int rows, int cols, bool is_row_major, cudaStream_t stream) + { + const int n_minor = is_row_major ? cols : rows; + if (ld == n_minor) { + tanh_kernel_nopad<<((size_t)rows * cols, 128), 128, 0, stream>>>( + inout, rows * cols, gain, offset); + } else { + int n1 = is_row_major ? cols : rows; + int n2 = is_row_major ? rows : cols; + auto [grid_shape, block_shape] = generateLaunchConfig2dElementwiseOp(n1, n2); + tanh_kernel<<>>(inout, ld, n1, n2, gain, offset); + } + RAFT_CUDA_TRY(cudaPeekAtLastError()); + } + + public: + /** + * Constructs a tanh kernel object. + * It evaluates the kernel matrix using the following formula: + * K_ij = tanh(gain* + offset) + * + * @tparam math_t floating point type + * @param gain + * @param offset + */ + TanhKernel(math_t gain, math_t offset) : GramMatrixBase(), gain(gain), offset(offset) {} + + [[deprecated]] TanhKernel(math_t gain, math_t offset, cublasHandle_t handle) + : GramMatrixBase(handle), gain(gain), offset(offset) + { + } + + /** Evaluate kernel matrix using tanh kernel. + * + * output_[i + k*n1] = (gain* + offset)^exponent, + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and < , > denotes dot product. + * + * @param [in] handle raft handle + * @param [in] x1 dense device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + void evaluate(raft::resources const& handle, + dense_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate kernel matrix using tanh kernel. + * + * output_[i + k*n1] = (gain* + offset)^exponent, + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and < , > denotes dot product. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate kernel matrix using tanh kernel. + * + * output_[i + k*n1] = (gain* + offset)^exponent, + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and < , > denotes dot product. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 csr device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 unused. + * @param norm_x2 unused. + */ + void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + csr_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate the Gram matrix using the legacy interface. + * + * @param [in] x1 device array of vectors, size [n1*n_cols] + * @param [in] n1 number vectors in x1 + * @param [in] n_cols number of columns (features) in x1 and x2 + * @param [in] x2 device array of vectors, size [n2*n_cols] + * @param [in] n2 number vectors in x2 + * @param [out] out device buffer to store the Gram matrix, size [n1*n2] + * @param [in] is_row_major whether the input and output matrices are in row + * major format + * @param [in] stream cuda stream + * @param ld1 leading dimension of x1 (usually it is n1) + * @param ld2 leading dimension of x2 (usually it is n2) + * @param ld_out leading dimension of out (usually it is n1) + */ + [[deprecated]] void evaluate(const math_t* x1, + int n1, + int n_cols, + const math_t* x2, + int n2, + math_t* out, + bool is_row_major, + cudaStream_t stream, + int ld1, + int ld2, + int ld_out) + { + ASSERT(GramMatrixBase::legacy_interface, + "Legacy interface can only be used with legacy ctor."); + GramMatrixBase::linear( + x1, n1, n_cols, x2, n2, out, is_row_major, stream, ld1, ld2, ld_out); + applyKernel(out, ld_out, n1, n2, is_row_major, stream); + } +}; + +/** + * Create a kernel matrix using RBF kernel function. + */ +template +class RBFKernel : public GramMatrixBase { + math_t gain; + + void applyKernel(math_t* inout, + int ld, + int rows, + int cols, + math_t* norm_x1, + math_t* norm_x2, + bool is_row_major, + cudaStream_t stream) + { + int n1 = is_row_major ? cols : rows; + int n2 = is_row_major ? rows : cols; + math_t* norm_n1 = is_row_major ? norm_x2 : norm_x1; + math_t* norm_n2 = is_row_major ? norm_x1 : norm_x2; + auto [grid_shape, block_shape] = generateLaunchConfig2dElementwiseOp(n1, n2); + rbf_kernel_expanded<<>>( + inout, ld, n1, n2, norm_n1, norm_n2, gain); + } + + public: + /** + * Constructs a RBF kernel object. + * It evaluates the kernel matrix using the following formula: + * K_ij = exp(-gain*|x1_i- x2_k|^2) + * + * @tparam math_t floating point type + * @param gain + */ + RBFKernel(math_t gain) : GramMatrixBase(), gain(gain) {} + + [[deprecated]] RBFKernel(math_t gain, cublasHandle_t handle) + : GramMatrixBase(handle), gain(gain) + { + } + + void matrixRowNormL2(raft::resources const& handle, + dense_input_matrix_view_t matrix, + math_t* target) + { + bool is_row_major = GramMatrixBase::get_is_row_major(matrix); + int minor = is_row_major ? matrix.extent(1) : matrix.extent(0); + int ld = is_row_major ? matrix.stride(0) : matrix.stride(1); + ASSERT(ld == minor, "RBF Kernel lazy rowNorm compute does not support ld parameter"); + raft::linalg::rowNorm(target, + matrix.data_handle(), + matrix.extent(1), + matrix.extent(0), + raft::linalg::NormType::L2Norm, + is_row_major, + resource::get_cuda_stream(handle)); + } + + void matrixRowNormL2(raft::resources const& handle, + csr_input_matrix_view_t matrix, + math_t* target) + { + auto matrix_structure = matrix.structure_view(); + raft::sparse::linalg::rowNormCsr(handle, + matrix_structure.get_indptr().data(), + matrix.get_elements().data(), + matrix_structure.get_nnz(), + matrix_structure.get_n_rows(), + target, + raft::linalg::NormType::L2Norm); + } + + /** Evaluate kernel matrix using RBF kernel. + * + * output_[i + k*n1] = exp(-gain*|x1_i - x2_k|^2), + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and | | euclidean distance. + * + * @param [in] handle raft handle + * @param [in] x1 dense device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 optional L2-norm of x1's rows for computation within RBF. + * @param norm_x2 optional L2-norm of x2's rows for computation within RBF. + */ + void evaluate(raft::resources const& handle, + dense_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + cudaStream_t stream = resource::get_cuda_stream(handle); + // lazy compute norms if not given + rmm::device_uvector tmp_norm_x1(0, stream); + rmm::device_uvector tmp_norm_x2(0, stream); + if (norm_x1 == nullptr) { + tmp_norm_x1.reserve(x1.extent(0), stream); + norm_x1 = tmp_norm_x1.data(); + matrixRowNormL2(handle, x1, norm_x1); + } + if (norm_x2 == nullptr) { + tmp_norm_x2.reserve(x2.extent(0), stream); + norm_x2 = tmp_norm_x2.data(); + matrixRowNormL2(handle, x2, norm_x2); + } + + // compute L2expanded + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + norm_x1, + norm_x2, + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate kernel matrix using RBF kernel. + * + * output_[i + k*n1] = exp(-gain*|x1_i - x2_k|^2), + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and | | euclidean distance. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 dense device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 optional L2-norm of x1's rows for computation within RBF. + * @param norm_x2 optional L2-norm of x2's rows for computation within RBF. + */ + void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + dense_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + cudaStream_t stream = resource::get_cuda_stream(handle); + + // lazy compute norms if not given + rmm::device_uvector tmp_norm_x1(0, stream); + rmm::device_uvector tmp_norm_x2(0, stream); + if (norm_x1 == nullptr) { + tmp_norm_x1.reserve(x1.structure_view().get_n_rows(), stream); + norm_x1 = tmp_norm_x1.data(); + matrixRowNormL2(handle, x1, norm_x1); + } + if (norm_x2 == nullptr) { + tmp_norm_x2.reserve(x2.extent(0), stream); + norm_x2 = tmp_norm_x2.data(); + matrixRowNormL2(handle, x2, norm_x2); + } + + // compute L2expanded + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + norm_x1, + norm_x2, + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate kernel matrix using RBF kernel. + * + * output_[i + k*n1] = exp(-gain*|x1_i - x2_k|^2), + * where x1_i is the i-th vector from the x1 set, and x2_k is k-th vector + * in the x2 set, and | | euclidean distance. + * + * @param [in] handle raft handle + * @param [in] x1 csr device matrix view, size [n1*n_cols] + * @param [in] x2 csr device matrix view, size [n2*n_cols] + * @param [out] out dense device matrix view for the Gram matrix, size [n1*n2] + * @param norm_x1 optional L2-norm of x1's rows for computation within RBF. + * @param norm_x2 optional L2-norm of x2's rows for computation within RBF. + */ + void evaluate(raft::resources const& handle, + csr_input_matrix_view_t x1, + csr_input_matrix_view_t x2, + dense_output_matrix_view_t out, + math_t* norm_x1, + math_t* norm_x2) + { + cudaStream_t stream = resource::get_cuda_stream(handle); + + // lazy compute norms if not given + rmm::device_uvector tmp_norm_x1(0, stream); + rmm::device_uvector tmp_norm_x2(0, stream); + if (norm_x1 == nullptr) { + tmp_norm_x1.reserve(x1.structure_view().get_n_rows(), stream); + norm_x1 = tmp_norm_x1.data(); + matrixRowNormL2(handle, x1, norm_x1); + } + if (norm_x2 == nullptr) { + tmp_norm_x2.reserve(x2.structure_view().get_n_rows(), stream); + norm_x2 = tmp_norm_x2.data(); + matrixRowNormL2(handle, x2, norm_x2); + } + + // compute L2expanded + bool is_row_major = GramMatrixBase::get_is_row_major(out); + int ld_out = is_row_major ? out.stride(0) : out.stride(1); + GramMatrixBase::linear(handle, x1, x2, out); + applyKernel(out.data_handle(), + ld_out, + out.extent(0), + out.extent(1), + norm_x1, + norm_x2, + is_row_major, + resource::get_cuda_stream(handle)); + } + + /** Evaluate the Gram matrix using the legacy interface. + * + * @param [in] x1 device array of vectors, size [n1*n_cols] + * @param [in] n1 number vectors in x1 + * @param [in] n_cols number of columns (features) in x1 and x2 + * @param [in] x2 device array of vectors, size [n2*n_cols] + * @param [in] n2 number vectors in x2 + * @param [out] out device buffer to store the Gram matrix, size [n1*n2] + * @param [in] is_row_major whether the input and output matrices are in row + * major format + * @param [in] stream cuda stream + * @param ld1 leading dimension of x1 (usually it is n1) + * @param ld2 leading dimension of x2 (usually it is n2) + * @param ld_out leading dimension of out (usually it is n1) + */ + [[deprecated]] void evaluate(const math_t* x1, + int n1, + int n_cols, + const math_t* x2, + int n2, + math_t* out, + bool is_row_major, + cudaStream_t stream, + int ld1, + int ld2, + int ld_out) + { + ASSERT(GramMatrixBase::legacy_interface, + "Legacy interface can only be used with legacy ctor."); + int minor1 = is_row_major ? n_cols : n1; + int minor2 = is_row_major ? n_cols : n2; + int minor_out = is_row_major ? n2 : n1; + ASSERT(ld1 == minor1, "RBF Kernel distance does not support ld1 parameter"); + ASSERT(ld2 == minor2, "RBF Kernel distance does not support ld2 parameter"); + ASSERT(ld_out == minor_out, "RBF Kernel distance does not support ld_out parameter"); + + math_t gain = this->gain; + using index_t = int64_t; + + rbf_fin_op fin_op{gain}; + + raft::resources handle; + resource::set_cuda_stream(handle, stream); + + cuvs::distance::distance(handle, + const_cast(x1), + const_cast(x2), + out, + n1, + n2, + n_cols, + NULL, + 0, + fin_op, + is_row_major); + } +}; + +}; // end namespace cuvs::distance::kernels::detail diff --git a/cpp/src/distance/detail/kernels/rbf_fin_op.cuh b/cpp/src/distance/detail/kernels/rbf_fin_op.cuh new file mode 100644 index 0000000000..73588baeae --- /dev/null +++ b/cpp/src/distance/detail/kernels/rbf_fin_op.cuh @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/* + * This file defines rbf_fin_op, which is used in GramMatrixBase. + * + * This struct has been moved to a separate file, so that it is cheap to include + * in distance/distance-ext.cuh, where an instance of cuvs::distance::distance + * with the rbf_fin_op is instantiated. + * + */ + +#include // raft::exp +#include // HD + +namespace cuvs::distance::kernels::detail { + +/** @brief: Final op for Gram matrix with RBF kernel. + * + * Calculates output = e^(-gain * in) + * + */ +template +struct rbf_fin_op { + OutT gain; + + explicit HD rbf_fin_op(OutT gain_) noexcept : gain(gain_) {} + + template + HDI OutT operator()(OutT d_val, Args... unused_args) + { + return raft::exp(-gain * d_val); + } +}; // struct rbf_fin_op + +} // namespace cuvs::distance::kernels::detail diff --git a/cpp/src/distance/detail/masked_distance_base.cuh b/cpp/src/distance/detail/masked_distance_base.cuh new file mode 100644 index 0000000000..d92052c84c --- /dev/null +++ b/cpp/src/distance/detail/masked_distance_base.cuh @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "../pairwise_distance_base.cuh" +#include +#include + +#include + +namespace cuvs { +namespace distance { +namespace detail { + +/** + * @brief Device class for masked nearest neighbor computations. + * + * @tparam useNorms whether norms are needed + * @tparam DataT input data-type (for x and y matrices) + * @tparam AccT accumulation data-type + * @tparam IdxT index data-type + * @tparam Policy struct which tunes the Contraction kernel + * @tparam CoreLambda tells how to accumulate an x and y into + acc. its signature: + template void core_lambda(AccT& acc, + const DataT& x, const DataT& y) + * @tparam EpilogueLambda applies an elementwise function to compute final + values. Its signature is: + template void epilogue_lambda + (AccT acc[][], DataT* regxn, DataT* regyn); + * @tparam FinalLambda the final lambda called on final distance value + * @tparam rowEpilogueLambda epilog lambda that executes when a full row has + * been processed. + * + * @param[in] x input matrix + * @param[in] y input matrix + * @param[in] m number of rows of x + * @param[in] n number of columns of y + * @param[in] k number of cols of x and y + * @param[in] lda leading dimension of x + * @param[in] ldb leading dimension of y + * @param[in] ldd parameter to keep Contractions_NT happy.. + * @param[in] xn row norms of input matrix A. Required for expanded L2, cosine + * @param[in] yn row norms of input matrix B. Required for expanded L2, cosine + * @param[in] adj An adjacency matrix encoded as a bitfield indicating for each + * row of `x` and each group in `y` whether to compute the + * distance. Dim = `(m / 64) x num_groups`. + * @param[in] group_idxs An array containing the *end* indices of each group + * in `y`. The value of group_idxs[j] indicates the + * start of group j + 1, i.e., it is the inclusive + * scan of the group lengths. The first group is + * always assumed to start at index 0 and the last + * group typically ends at index `n`. Length = + * `num_groups`. + * @param[in] num_groups The number of groups in group_idxs. + * @param[in] smem shared mem buffer for intermediate storage of x, y, xn & yn. + * @param core_op the core accumulation operation lambda + * @param epilog_op the epilog operation lambda + * @param fin_op the final gemm epilogue lambda + * @param rowEpilog_op epilog lambda that executes when a full row has been processed. + */ +template > +struct MaskedDistances : public BaseClass { + private: + typedef Policy P; + const DataT* xn; + const DataT* yn; + const DataT* const yBase; + const uint64_t* adj; + const IdxT* group_idxs; + IdxT num_groups; + char* smem; + CoreLambda core_op; + EpilogueLambda epilog_op; + FinalLambda fin_op; + rowEpilogueLambda rowEpilog_op; + + AccT acc[P::AccRowsPerTh][P::AccColsPerTh]; + + public: + // Constructor + DI MaskedDistances(const DataT* _x, + const DataT* _y, + IdxT _m, + IdxT _n, + IdxT _k, + IdxT _lda, + IdxT _ldb, + IdxT _ldd, + const DataT* _xn, + const DataT* _yn, + const uint64_t* _adj, + const IdxT* _group_idxs, + IdxT _num_groups, + char* _smem, + CoreLambda _core_op, + EpilogueLambda _epilog_op, + FinalLambda _fin_op, + rowEpilogueLambda _rowEpilog_op) + : BaseClass(_x, _y, _m, _n, _k, _lda, _ldb, _ldd, _smem), + xn(_xn), + yn(_yn), + yBase(_y), + adj(_adj), + group_idxs(_group_idxs), + num_groups(_num_groups), + smem(_smem), + core_op(_core_op), + epilog_op(_epilog_op), + fin_op(_fin_op), + rowEpilog_op(_rowEpilog_op) + { + } + + DI void run() + { + const auto grid_stride_m = (P::Mblk * gridDim.y); + const auto grid_offset_m = (P::Mblk * blockIdx.y); + + const auto grid_stride_g = gridDim.x; + const auto grid_offset_g = blockIdx.x; + + for (auto tile_idx_m = grid_offset_m; tile_idx_m < this->m; tile_idx_m += grid_stride_m) { + // Start loop over groups + for (auto idx_g = grid_offset_g; idx_g < this->num_groups; idx_g += grid_stride_g) { + const uint64_t block_adj = get_block_adjacency(adj, tile_idx_m, idx_g); + // block_adj is a bitfield that contains a 1 if a row is adjacent to the + // current group. All zero means we can skip this group. + if (block_adj == 0) { continue; } + + // thread_adj is a bitfield that contains a 1 at location i iff we must + // compute row i of acc (the accumulator register tile). That is, + // for i = 0,.., AccRowsPerTh and j = 0,.., AccColsPerTh: + // + // ((1 << i) & thread_adj) > 0 <=> acc[i][j] must be computed. + // + // We precompute this information because it is used in various + // locations to skip thread-local computations, specifically: + // + // 1. To skip computations if thread_adj == 0, i.e., none of the values + // of `acc` have to be computed. + // + // 2. In epilog_op, to consider only values of `acc` to be reduced that + // are not masked of. + // + // Note 1: Even when the computation can be skipped for a specific thread, + // the thread still participates in synchronization operations. + // + // Note 2: In theory, it should be possible to skip computations for + // specific rows of `acc`. In practice, however, this does not improve + // performance. + int thread_adj = compute_thread_adjacency(block_adj); + + auto tile_idx_n = idx_g == 0 ? 0 : group_idxs[idx_g - 1]; + const auto group_end_n = group_idxs[idx_g]; + for (; tile_idx_n < group_end_n; tile_idx_n += P::Nblk) { + // We provide group_end_n to limit the number of unnecessary data + // points that are loaded from y. + this->ldgXY(tile_idx_m, tile_idx_n, 0, group_end_n); + + reset_accumulator(); + this->stsXY(); + __syncthreads(); + this->switch_write_buffer(); + + for (int kidx = P::Kblk; kidx < this->k; kidx += P::Kblk) { + this->ldgXY(tile_idx_m, tile_idx_n, kidx, group_end_n); + // Process all data in shared memory (previous k-block) and + // accumulate in registers. + if (thread_adj != 0) { accumulate(); } + this->stsXY(); + __syncthreads(); + this->switch_write_buffer(); + this->switch_read_buffer(); + } + if (thread_adj != 0) { + accumulate(); // last iteration + } + // The pre-condition for the loop over tile_idx_n is that write_buffer + // and read_buffer point to the same buffer. This flips read_buffer + // back so that it satisfies the pre-condition of this loop. + this->switch_read_buffer(); + + if (useNorms) { + DataT regxn[P::AccRowsPerTh], regyn[P::AccColsPerTh]; + load_norms(tile_idx_m, tile_idx_n, group_end_n, regxn, regyn); + if (thread_adj != 0) { + epilog_op(acc, thread_adj, regxn, regyn, tile_idx_n, tile_idx_m, group_end_n); + } + } else { + if (thread_adj != 0) { + epilog_op(acc, thread_adj, nullptr, nullptr, tile_idx_n, tile_idx_m, group_end_n); + } + } + } // tile_idx_n + } // idx_g + rowEpilog_op(tile_idx_m); + } // tile_idx_m + } + + private: + DI uint64_t get_block_adjacency(const uint64_t* adj, IdxT tile_idx_m, IdxT idx_group) + { + // A single element of `adj` contains exactly enough bits to indicate which + // rows in the current tile to skip and which to compute. + static_assert(P::Mblk == 8 * sizeof(adj[0]), + "masked_l2_nn only supports a policy with 64 rows per block."); + IdxT block_flag_idx = tile_idx_m / P::Mblk; + // Index into adj at row tile_idx_m / 64 and column idx_group. + return adj[block_flag_idx * this->num_groups + idx_group]; + } + + DI uint32_t compute_thread_adjacency(const uint64_t block_adj) + { + // thread_adj is a bitfield that contains a 1 at location i iff we must + // compute row i of acc (the accumulator register tile). It is described in + // more detail in the run() method. + uint32_t thread_adj = 0; +#pragma unroll + for (int thread_row_idx = 0; thread_row_idx < P::AccRowsPerTh; ++thread_row_idx) { + // Index `thread_row_idx` refers to a row of the current threads' register + // tile `acc`, i.e., acc[i][:]. Index `block_row_idx` refers to the + // corresponding row of the current block tile in shared memory. + const int block_row_idx = this->accrowid + thread_row_idx * P::AccThRows; + + // block_row_is_adjacent is true if the current block_row_idx is adjacent + // to the current group. + const uint64_t block_mask = 1ull << block_row_idx; + const bool block_row_is_adjacent = (block_adj & block_mask) != 0; + if (block_row_is_adjacent) { + // If block row is adjacent, write a 1 bit to thread_adj at location + // `thread_row_idx`. + const uint32_t thread_mask = 1 << thread_row_idx; + thread_adj |= thread_mask; + } + } + return thread_adj; + } + + DI void reset_accumulator() + { + // Reset accumulator registers to zero. +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + acc[i][j] = BaseClass::Zero; + } + } + } + + DI void accumulate() + { +#pragma unroll + for (int ki = 0; ki < P::Kblk; ki += P::Veclen) { + this->ldsXY(ki); +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { +#pragma unroll + for (int v = 0; v < P::Veclen; ++v) { + core_op(acc[i][j], this->regx[i][v], this->regy[j][v]); + } + } + } + } + } + + DI void load_norms(IdxT tile_idx_m, + IdxT tile_idx_n, + IdxT end_n, + DataT (®xn)[P::AccRowsPerTh], + DataT (®yn)[P::AccColsPerTh]) + { + DataT* sxNorm = (DataT*)(&smem[P::SmemSize]); + DataT* syNorm = (&sxNorm[P::Mblk]); + + // Load x & y norms required by this threadblock in shmem buffer + for (int i = threadIdx.x; i < P::Mblk; i += P::Nthreads) { + auto idx = tile_idx_m + i; + sxNorm[i] = idx < this->m ? xn[idx] : 0; + } + + for (int i = threadIdx.x; i < P::Nblk; i += P::Nthreads) { + auto idx = tile_idx_n + i; + syNorm[i] = idx < end_n ? yn[idx] : 0; + } + __syncthreads(); + +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + regxn[i] = sxNorm[i * P::AccThRows + (threadIdx.x / P::AccThCols)]; + } +#pragma unroll + for (int i = 0; i < P::AccColsPerTh; ++i) { + regyn[i] = syNorm[i * P::AccThCols + (threadIdx.x % P::AccThCols)]; + } + } +}; // struct MaskedDistances + +}; // namespace detail +}; // namespace distance +}; // namespace cuvs diff --git a/cpp/src/distance/detail/masked_nn.cuh b/cpp/src/distance/detail/masked_nn.cuh new file mode 100644 index 0000000000..6520b1e2e9 --- /dev/null +++ b/cpp/src/distance/detail/masked_nn.cuh @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../compress_to_bits.cuh" +#include "../fused_distance_nn/fused_l2_nn.cuh" +#include "../masked_distance_base.cuh" +#include +#include +#include +#include + +#include + +#include + +#include + +namespace cuvs { +namespace distance { +namespace detail { + +template +__launch_bounds__(P::Nthreads, 2) RAFT_KERNEL masked_l2_nn_kernel(OutT* min, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + const uint64_t* adj, + const IdxT* group_idxs, + IdxT num_groups, + IdxT m, + IdxT n, + IdxT k, + bool sqrt, + DataT maxVal, + int* mutex, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + CoreLambda core_op, + FinalLambda fin_op) +{ + extern __shared__ char smem[]; + + typedef raft::KeyValuePair KVPair; + KVPair val[P::AccRowsPerTh]; +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + val[i] = {-1, maxVal}; + } + + // epilogue operation lambda for final value calculation + auto epilog_lambda = [pairRedOp, &val, maxVal, sqrt] __device__( + DataT acc[P::AccRowsPerTh][P::AccColsPerTh], + int thread_adj, + DataT* regxn, + DataT* regyn, + IdxT tile_idx_n, + IdxT tile_idx_m, + IdxT tile_end_n) { + KVPReduceOpT pairRed_op(pairRedOp); + +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + acc[i][j] = regxn[i] + regyn[j] - (DataT)2.0 * acc[i][j]; + } + } + if (sqrt) { +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + acc[i][j] = raft::sqrt(acc[i][j]); + } + } + } + + // intra thread reduce + const auto acccolid = threadIdx.x % P::AccThCols; + const auto accrowid = threadIdx.x / P::AccThCols; + +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + // thread_adj is a bitfield that contains a 1 at location i iff we must + // compute row i of acc (the accumulator register tile). It is described in + // more detail in the maskedDistances.run() method. + const bool ignore = (thread_adj & (1 << i)) == 0; + if (ignore) { continue; } +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + auto tmpkey = acccolid + j * P::AccThCols + tile_idx_n; + if (tile_end_n <= tmpkey) { + // Do not process beyond end of tile. + continue; + } + KVPair tmp = {tmpkey, acc[i][j]}; + if (tmpkey < tile_end_n) { + val[i] = pairRed_op(accrowid + i * P::AccThRows + tile_idx_m, tmp, val[i]); + } + } + } + }; + + auto rowEpilog_lambda = + [m, mutex, min, pairRedOp, redOp, &val, maxVal] __device__(IdxT tile_idx_m) { + KVPReduceOpT pairRed_op(pairRedOp); + ReduceOpT red_op(redOp); + + const auto accrowid = threadIdx.x / P::AccThCols; + const auto lid = raft::laneId(); + // reduce +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = P::AccThCols / 2; j > 0; j >>= 1) { + auto tmpkey = raft::shfl(val[i].key, lid + j); + auto tmpvalue = raft::shfl(val[i].value, lid + j); + KVPair tmp = {tmpkey, tmpvalue}; + val[i] = pairRed_op(accrowid + i * P::AccThRows + tile_idx_m, tmp, val[i]); + } + } + + updateReducedVal(mutex, min, val, red_op, m, tile_idx_m); + + // reset the val array. +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + val[i] = {-1, maxVal}; + } + }; + + IdxT lda = k, ldb = k, ldd = n; + MaskedDistances + obj(x, + y, + m, + n, + k, + lda, + ldb, + ldd, + xn, + yn, + adj, + group_idxs, + num_groups, + smem, + core_op, + epilog_lambda, + fin_op, + rowEpilog_lambda); + obj.run(); +} + +/** + * @brief Wrapper for masked_l2_nn_kernel + * + * Responsibilities: + * - Allocate (and initialize) workspace memory for: + * - mutexes used in nearest neighbor update step + * - adjacency matrix bitfield + * - Compress adjacency matrix to bitfield + * - Initialize output buffer (conditional on `initOutBuffer`) + * - Specify core and final operations for the L2 norm + * - Determine optimal launch configuration for kernel. + * - Launch kernel and check for errors. + * + * @tparam DataT Input data-type (for x and y matrices). + * @tparam OutT Output data-type (for key-value pairs). + * @tparam IdxT Index data-type. + * @tparam ReduceOpT A struct to perform the final needed reduction + * operation and also to initialize the output array + * elements with the appropriate initial value needed for + * reduction. + * @tparam KVPReduceOpT Type of Reduction operation on key value pairs. + * + * @param handle RAFT handle for managing expensive resources + * @param[out] out Will contain reduced output (nn key-value pairs) + * @param[in] x First matrix. Row major. Dim = `m x k`. (on device) + * @param[in] y Second matrix. Row major. Dim = `n x k`. (on device) + * @param[in] xn L2 squared norm of `x`. Length = `m`. + * @param[in] yn L2 squared norm of `y`. Length = `n`. + * @param[in] adj A boolean adjacency matrix indicating for each + * row of `x` and each group in `y` whether to compute the + * distance. Dim = `m x num_groups`. + * @param[in] group_idxs An array containing the *end* indices of each group + * in `y`. The value of group_idxs[j] indicates the + * start of group j + 1, i.e., it is the inclusive + * scan of the group lengths. The first group is + * always assumed to start at index 0 and the last + * group typically ends at index `n`. Length = + * `num_groups`. + * @param[in] num_groups Length of `group_idxs`. + * @param m Rows of `x`. + * @param n Rows of `y`. + * @param k Cols of `x` and `y`. + * @param redOp Reduction operator in the epilogue + * @param pairRedOp Reduction operation on key value pairs + * @param sqrt Whether to compute the squared or actual (i.e. sqrt) L2 norm. + * @param initOutBuffer Whether to initialize the output buffer + * + * + */ +template +void masked_l2_nn_impl(raft::resources const& handle, + OutT* out, + const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + const bool* adj, + const IdxT* group_idxs, + IdxT num_groups, + IdxT m, + IdxT n, + IdxT k, + ReduceOpT redOp, + KVPReduceOpT pairRedOp, + bool sqrt, + bool initOutBuffer) +{ + typedef typename linalg::Policy4x4::Policy P; + + static_assert(P::Mblk == 64, "masked_l2_nn_impl only supports a policy with 64 rows per block."); + + // Get stream and workspace memory resource + rmm::mr::device_memory_resource* ws_mr = + dynamic_cast(resource::get_workspace_resource(handle)); + auto stream = resource::get_cuda_stream(handle); + + // Acquire temporary buffers and initialize to zero: + // 1) Adjacency matrix bitfield + // 2) Workspace for fused nearest neighbor operation + size_t m_div_64 = raft::ceildiv(m, IdxT(64)); + rmm::device_uvector ws_adj64{m_div_64 * num_groups, stream, ws_mr}; + rmm::device_uvector ws_fused_nn{size_t(m), stream, ws_mr}; + RAFT_CUDA_TRY(cudaMemsetAsync(ws_adj64.data(), 0, ws_adj64.size() * sizeof(uint64_t), stream)); + RAFT_CUDA_TRY(cudaMemsetAsync(ws_fused_nn.data(), 0, ws_fused_nn.size() * sizeof(int), stream)); + + // Compress boolean adjacency matrix to bitfield. + auto adj_view = raft::make_device_matrix_view(adj, m, num_groups); + auto adj64_view = + raft::make_device_matrix_view(ws_adj64.data(), m_div_64, num_groups); + compress_to_bits(handle, adj_view, adj64_view); + + // Initialize output buffer with keyvalue pairs as determined by the reduction + // operator (it will be called with maxVal). + constexpr auto maxVal = std::numeric_limits::max(); + if (initOutBuffer) { + dim3 grid(raft::ceildiv(m, P::Nthreads)); + dim3 block(P::Nthreads); + + initKernel<<>>(out, m, maxVal, redOp); + RAFT_CUDA_TRY(cudaGetLastError()); + } + + // Accumulation operation lambda + auto core_lambda = [] __device__(DataT & acc, DataT & x, DataT & y) { acc += x * y; }; + auto fin_op = raft::identity_op{}; + + auto kernel = masked_l2_nn_kernel; + constexpr size_t smemSize = P::SmemSize + ((P::Mblk + P::Nblk) * sizeof(DataT)); + dim3 block(P::Nthreads); + dim3 grid = launchConfigGenerator

(m, n, smemSize, kernel); + + kernel<<>>(out, + x, + y, + xn, + yn, + ws_adj64.data(), + group_idxs, + num_groups, + m, + n, + k, + sqrt, + maxVal, + ws_fused_nn.data(), + redOp, + pairRedOp, + core_lambda, + fin_op); + + RAFT_CUDA_TRY(cudaGetLastError()); +} + +} // namespace detail +} // namespace distance +} // namespace cuvs diff --git a/cpp/src/distance/detail/pairwise_distance_base.cuh b/cpp/src/distance/detail/pairwise_distance_base.cuh new file mode 100644 index 0000000000..990f845fd4 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_distance_base.cuh @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include // raft::linalg::Contractions_NT +#include // ceildiv +#include // RAFT_CUDA_TRY + +#include // size_t + +namespace cuvs { +namespace distance { +namespace detail { + +/** + * @brief Device class for L1, L2 and cosine distance metrics. + * @tparam DataT input data-type (for A and B matrices) + * @tparam AccT accumulation data-type + * @tparam OutT output data-type (for C and D matrices) + * @tparam IdxT index data-type + * @tparam Policy struct which tunes the Contraction kernel + * @tparam OpT A distance operation, e.g., cosine_distance_op. + * @tparam EpilogueLambda applies an elementwise function to compute final + values. Its signature is: + template void epilogue_lambda + (AccT acc[][], DataT* regxn, DataT* regyn); + * @tparam FinalLambda the final lambda called on final distance value + * @param[in] x input matrix + * @param[in] y input matrix + * @param[in] m number of rows of A and C/D + * @param[in] n number of columns of B and C/D + * @param[in] k number of cols of A and rows of B + * @param[in] lda leading dimension of A + * @param[in] ldb leading dimension of B + * @param[in] ldd leading dimension of C/D + * @param[in] xn row norms of input matrix A. Required for expanded L2, cosine + * @param[in] yn row norms of input matrix B. Required for expanded L2, cosine + * @param[output] pD output matrix + * @param[in] smem shared mem buffer for intermediate storage of A, B, xn & yn. + * @param distance_op the distance operation, e.g. cosine_distance_op + * @param epilog_op the epilog operation lambda + * @param fin_op the final gemm epilogue lambda + * @param rowEpilog_op epilog lambda that executes when a full row has been processed + */ + +template > +struct PairwiseDistances : public BaseClass { + // Get accumulation type from distance_op + using AccT = typename OpT::AccT; + + private: + typedef Policy P; + const DataT* xn; + const DataT* yn; + const DataT* const yBase; + OutT* dOutput; + char* smem; + OpT distance_op; + EpilogueLambda epilog_op; + FinalLambda fin_op; + rowEpilogueLambda rowEpilog_op; + + const IdxT grid_stride_m; + const IdxT grid_stride_n; + const IdxT grid_offset_m; + const IdxT grid_offset_n; + + AccT acc[P::AccRowsPerTh][P::AccColsPerTh]; + + public: + // Constructor + DI PairwiseDistances(const DataT* _x, + const DataT* _y, + IdxT _m, + IdxT _n, + IdxT _k, + IdxT _lda, + IdxT _ldb, + IdxT _ldd, + const DataT* _xn, + const DataT* _yn, + OutT* _dOutput, + char* _smem, + OpT _distance_op, + EpilogueLambda _epilog_op, + FinalLambda _fin_op, + rowEpilogueLambda _rowEpilog_op) + : BaseClass(_x, _y, _m, _n, _k, _lda, _ldb, _ldd, _smem), + xn(_xn), + yn(_yn), + yBase(_y), + dOutput(_dOutput), + smem(_smem), + distance_op(_distance_op), + epilog_op(_epilog_op), + fin_op(_fin_op), + rowEpilog_op(_rowEpilog_op), + grid_stride_m(P::Mblk * gridDim.y), + grid_stride_n(P::Nblk * gridDim.x), + grid_offset_m(P::Mblk * blockIdx.y), + grid_offset_n(P::Nblk * blockIdx.x) + { + } + + DI void run() + { + for (auto tile_idx_m = grid_offset_m; tile_idx_m < this->m; tile_idx_m += grid_stride_m) { + this->ldgXY(tile_idx_m, grid_offset_n, 0); + for (auto tile_idx_n = grid_offset_n; tile_idx_n < this->n; tile_idx_n += grid_stride_n) { + // Prolog: + reset_accumulator(); + this->stsXY(); + __syncthreads(); + this->switch_write_buffer(); + + // Main loop: + for (int kidx = P::Kblk; kidx < this->k; kidx += P::Kblk) { + this->ldgXY(tile_idx_m, tile_idx_n, kidx); + // Process all data in shared memory (previous k-block) and + // accumulate in registers. + accumulate(); + this->stsXY(); + __syncthreads(); + this->switch_write_buffer(); + this->switch_read_buffer(); + } + accumulate(); // last iteration + // The pre-condition for the loop over tile_idx_n is that write_buffer + // and read_buffer point to the same buffer. This flips read_buffer back + // so that it satisfies the pre-condition of this loop. + this->switch_read_buffer(); + + // Epilog: + if (distance_op.use_norms) { + DataT regxn[P::AccRowsPerTh], regyn[P::AccColsPerTh]; + load_norms(tile_idx_m, tile_idx_n, regxn, regyn); + // Overlap ldg with epilog computation + ldgNextGridStride(tile_idx_m, tile_idx_n); + // Calculate distance_op epilog. + // Use .template to disambiguate (See: + // https://en.cppreference.com/w/cpp/language/dependent_name) + distance_op.template epilog(acc, regxn, regyn, tile_idx_n, tile_idx_m); + // And any possible additional epilogs + epilog_op(acc, regxn, regyn, tile_idx_n, tile_idx_m); + } else { + // Overlap ldg with epilog computation + ldgNextGridStride(tile_idx_m, tile_idx_n); + // Calculate distance_op epilog. + // Use .template to disambiguate (See: + // https://en.cppreference.com/w/cpp/language/dependent_name) + distance_op.template epilog(acc, nullptr, nullptr, tile_idx_n, tile_idx_m); + // And any possible additional epilogs + epilog_op(acc, nullptr, nullptr, tile_idx_n, tile_idx_m); + } + if (writeOut) { store_output(tile_idx_m, tile_idx_n); } + } + rowEpilog_op(tile_idx_m); + } + } + + private: + DI void ldgNextGridStride(IdxT tile_idx_m, IdxT tile_idx_n) + { + // Fetch next grid stride ldg if within range + const auto next_tile_tile_idx_n = tile_idx_n + grid_stride_n; + const auto next_tile_tile_idx_m = tile_idx_m + grid_stride_m; + if ((next_tile_tile_idx_n) < this->n) { + this->ldgXY(tile_idx_m, next_tile_tile_idx_n, 0); + } else if ((next_tile_tile_idx_m) < this->m) { + this->ldgXY(next_tile_tile_idx_m, grid_offset_n, 0); + } + } + + DI void reset_accumulator() + { + // Reset accumulator registers to zero. +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + acc[i][j] = BaseClass::Zero; + } + } + } + + DI void accumulate_reg_tile(DataT (®_x)[P::AccRowsPerTh][P::Veclen], + DataT (®_y)[P::AccColsPerTh][P::Veclen]) + { +#pragma unroll + for (int v = 0; v < P::Veclen; ++v) { +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + distance_op.core(acc[i][j], reg_x[i][v], reg_y[j][v]); + } + } + } + } + + DI void accumulate() + { + // We have a separate ldsXY and accumulate_reg_tile outside the loop body, + // so that these separated calls can be interspersed with preceding and + // following instructions, thereby hiding latency. + this->ldsXY(0); + + // If expensive inner loop, do not unroll loop. + constexpr int num_iterations = P::Kblk / P::Veclen - 1; + constexpr int unroll_count = decltype(distance_op)::expensive_inner_loop ? 1 : num_iterations; +#pragma unroll unroll_count + for (int ki = P::Veclen; ki < P::Kblk; ki += P::Veclen) { + accumulate_reg_tile(this->regx, this->regy); + this->ldsXY(ki); + } + + // Accumulate last loaded tile. + accumulate_reg_tile(this->regx, this->regy); + } + + DI void load_norms(IdxT tile_idx_m, + IdxT tile_idx_n, + DataT (®xn)[P::AccRowsPerTh], + DataT (®yn)[P::AccColsPerTh]) + { + DataT* sxNorm = (DataT*)(&smem[P::SmemSize]); + DataT* syNorm = (&sxNorm[P::Mblk]); + + // Load x & y norms required by this threadblock in shmem buffer + if (tile_idx_n == blockIdx.x * P::Nblk) { + for (int i = threadIdx.x; i < P::Mblk; i += P::Nthreads) { + auto idx = tile_idx_m + i; + sxNorm[i] = idx < this->m ? xn[idx] : 0; + } + } + + for (int i = threadIdx.x; i < P::Nblk; i += P::Nthreads) { + auto idx = tile_idx_n + i; + syNorm[i] = idx < this->n ? yn[idx] : 0; + } + __syncthreads(); + +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + regxn[i] = sxNorm[i * P::AccThRows + (threadIdx.x / P::AccThCols)]; + } +#pragma unroll + for (int i = 0; i < P::AccColsPerTh; ++i) { + regyn[i] = syNorm[i * P::AccThCols + (threadIdx.x % P::AccThCols)]; + } + } + + DI void store_output(IdxT tile_idx_m, IdxT tile_idx_n) + { + IdxT starty = tile_idx_m + this->accrowid; + IdxT startx = tile_idx_n + this->acccolid; + +#pragma unroll + for (int i = 0; i < P::AccRowsPerTh; ++i) { + auto rowId = starty + i * P::AccThRows; +#pragma unroll + for (int j = 0; j < P::AccColsPerTh; ++j) { + auto colId = startx + j * P::AccThCols; + if (rowId < this->m && colId < this->n) { + // Promote to 64 bit index for final write, as output array can be > 2^31 + dOutput[std::size_t(rowId) * this->n + colId] = fin_op(acc[i][j], 0); + } + } + } + } +}; // struct PairwiseDistances + +template +dim3 launchConfigGenerator(IdxT m, IdxT n, std::size_t sMemSize, T func) +{ + int devId; + RAFT_CUDA_TRY(cudaGetDevice(&devId)); + int numSMs; + RAFT_CUDA_TRY(cudaDeviceGetAttribute(&numSMs, cudaDevAttrMultiProcessorCount, devId)); + + int numBlocksPerSm = 0; + dim3 grid; + + RAFT_CUDA_TRY( + cudaOccupancyMaxActiveBlocksPerMultiprocessor(&numBlocksPerSm, func, P::Nthreads, sMemSize)); + std::size_t minGridSize = numSMs * numBlocksPerSm; + std::size_t yChunks = raft::ceildiv(m, P::Mblk); + std::size_t xChunks = raft::ceildiv(n, P::Nblk); + grid.y = yChunks > minGridSize ? minGridSize : yChunks; + grid.x = (minGridSize - grid.y) <= 0 ? 1 : xChunks; + if (grid.x != 1) { + std::size_t i = 1; + while (grid.y * i < minGridSize) { + i++; + } + grid.x = i >= xChunks ? xChunks : i; + } + + return grid; +} + +}; // namespace detail +}; // namespace distance +}; // namespace cuvs diff --git a/cpp/src/distance/detail/pairwise_distance_cutlass_base.cuh b/cpp/src/distance/detail/pairwise_distance_cutlass_base.cuh new file mode 100644 index 0000000000..da9a1ac4eb --- /dev/null +++ b/cpp/src/distance/detail/pairwise_distance_cutlass_base.cuh @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#pragma GCC diagnostic ignored "-Wtautological-compare" + +// We define CUTLASS_NAMESPACE in case +// RAFT cmake is not used +#ifndef CUTLASS_NAMESPACE +#define cutlass raft_cutlass +#endif + +#include "pairwise_distance_epilogue_elementwise.h" +#include "pairwise_distance_gemm.h" + +#include "distance_ops/cutlass.cuh" +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace cuvs { +namespace distance { +namespace detail { + +template +std::enable_if_t::value> cutlassDistanceKernel(const DataT* x, + const DataT* y, + const DataT* xn, + const DataT* yn, + IdxT m, + IdxT n, + IdxT k, + IdxT lda, + IdxT ldb, + IdxT ldd, + OutT* dOutput, + FinalLambda fin_op, + OpT distance_op, + cudaStream_t stream) +{ + static_assert(!(std::is_same::value), + "OutType bool is not supported use uint8_t instead"); + + auto dist_op = distance_op.get_cutlass_op(); + using DistanceFn = decltype(dist_op); + using EpilogueOutputOp = + cutlass::epilogue::thread::PairwiseDistanceEpilogueElementwise; + constexpr int batch_count = 1; + + constexpr auto mode = cutlass::gemm::GemmUniversalMode::kGemm; + + typename EpilogueOutputOp::Params epilog_op_param(dist_op, fin_op); + + // Number of pipelines you want to use + constexpr int NumStages = 3; + // Alignment + constexpr int Alignment = VecLen; + + using cutlassDistKernel = + typename cutlass::gemm::kernel::PairwiseDistanceGemm::GemmKernel; + + using cutlassDist = cutlass::gemm::device::GemmUniversalAdapter; + + constexpr uint32_t gridYZMax = ((1 << (sizeof(uint16_t) * 8)) - 1); + constexpr uint32_t max_batch_size = gridYZMax * cutlassDistKernel::ThreadblockShape::kN; + IdxT numNbatches = (n - 1 + max_batch_size) / max_batch_size; + + for (IdxT i = 0; i < numNbatches; i++) { + const DataT *a, *b; + IdxT gemm_lda, gemm_ldb; + size_t offsetN = i * max_batch_size; + + if constexpr (isRowMajor) { + gemm_lda = ldb; + gemm_ldb = lda; + a = y + offsetN * gemm_lda; + b = x; + } else { + gemm_lda = lda; + gemm_ldb = ldb; + a = x; + b = y + offsetN; + } + IdxT chunkN = (i + 1) * max_batch_size; + IdxT currentN = (chunkN < n) ? max_batch_size : (n - offsetN); + + // default initialize problem size with row major inputs + auto problem_size = isRowMajor ? cutlass::gemm::GemmCoord(currentN, m, k) + : cutlass::gemm::GemmCoord(m, currentN, k); + + typename cutlassDist::Arguments arguments{ + mode, + problem_size, + batch_count, + epilog_op_param, + a, + b, + xn, // C matrix eq vector param, which here is A norm + nullptr, // tensor_Z, + (DataT*)yn + offsetN, // this is broadcast vec, which is required to be non-const param + dOutput + offsetN, // Output distance matrix + (int64_t)0, // batch stride A + (int64_t)0, // batch stride B + (int64_t)0, // batch stride Norm A + (int64_t)0, + (int64_t)0, // batch stride Norm B + (int64_t)0, // batch stride Output + gemm_lda, // stride A + gemm_ldb, // stride B + 1, // stride A norm + 0, // this is no-op for Z + 0, // This must be zero + ldd // stride Output matrix + }; + + // Using the arguments, query for extra workspace required for matrix multiplication computation + size_t workspace_size = cutlassDist::get_workspace_size(arguments); + // Allocate workspace memory + rmm::device_uvector workspace(workspace_size, stream); + // Instantiate CUTLASS kernel depending on templates + cutlassDist cutlassDist_op; + // Check the problem size is supported or not + RAFT_CUTLASS_TRY(cutlassDist_op.can_implement(arguments)); + + // Initialize CUTLASS kernel with arguments and workspace pointer + RAFT_CUTLASS_TRY(cutlassDist_op.initialize(arguments, workspace.data(), stream)); + + // Launch initialized CUTLASS kernel + RAFT_CUTLASS_TRY(cutlassDist_op(stream)); + } +} + +}; // namespace detail +}; // namespace distance +}; // namespace cuvs + +#pragma GCC diagnostic pop diff --git a/cpp/src/distance/detail/pairwise_distance_epilogue.h b/cpp/src/distance/detail/pairwise_distance_epilogue.h new file mode 100644 index 0000000000..6ead09ed16 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_distance_epilogue.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! \file + \brief Epilogue for threadblock scoped GEMMs using Tensor Ops. + +This is adapted from DefaultEpilogueWithBroadcastTensorOp from CUTLASS 2.9.0 +(https://github.com/NVIDIA/cutlass/blob/master/include/cutlass/epilogue/threadblock/default_epilogue_with_broadcast.h#L75) + +This epilogue allows us to load norm buffers using PredicatedTileIteratorNormVec +and EpilogueWithBroadcast used for distances L2/cosine as well as applies user-define elementwise +operation. +-- A norm load is provided PredicatedTileIteratorNormVec +-- B norm load is provided by EpilogueWithBroadcast +-- elementwise operation is provided by OutputOp +*/ + +#pragma once + +#include "./predicated_tile_iterator_normvec.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace epilogue { +namespace threadblock { + +//////////////////////////////////////////////////////////////////////////////// + +/// Defines sensible defaults for epilogues for TensorOps. +template +struct PairwiseDistanceEpilogue { + /// Use defaults related to the existing epilogue + using Base = + DefaultEpilogueTensorOp; + + // + // Stores the result z = (y = GEMM(A, B, C), broadcast) + // + using OutputTileIterator = cutlass::epilogue::threadblock:: + PredicatedTileIteratorNormVec; + + // + // Additional tensor tile iterator - stores t = Elementwise(z) + // + using TensorTileIterator = + cutlass::epilogue::threadblock::PredicatedTileIterator; + + /// Define the epilogue + using Epilogue = EpilogueWithBroadcast; +}; + +} // namespace threadblock +} // namespace epilogue +} // namespace cutlass + +//////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/pairwise_distance_epilogue_elementwise.h b/cpp/src/distance/detail/pairwise_distance_epilogue_elementwise.h new file mode 100644 index 0000000000..2b2c04b9d3 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_distance_epilogue_elementwise.h @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +/*! \file + \brief Functor performing distance operations used by epilogues of pairwise distance + * kernels. +* This is adapted from LinearCombinationBiasElementwise from CUTLASS 2.9.0 +* customized for applying elementwise distance formula on accumulated GEMM value +* and applying user-defined final custom operation on the distance value. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace epilogue { +namespace thread { + +///////////////////////////////////////////////////////////////////////////////////////////////// + +/// This base class is meant to define the concept required of the +/// EpilogueWithBroadcast::OutputOp +template +class PairwiseDistanceEpilogueElementwise { + public: + using ElementOutput = ElementC_; + using ElementC = ElementC_; + using ElementAccumulator = ElementAccumulator_; + using ElementCompute = ElementCompute_; + using ElementZ = ElementZ_; + using ElementT = ElementT_; + static int const kElementsPerAccess = ElementsPerAccess; + static int const kCount = kElementsPerAccess; + + using DistanceOp = DistanceOp_; + using FinalOp = FinalOp_; + + using FragmentAccumulator = Array; + using FragmentCompute = Array; + using FragmentC = Array; + using FragmentZ = Array; + using FragmentT = Array; + + using FragmentOutput = FragmentZ; + + static bool const kIsHeavy = false; // ElementwiseOp::kIsHeavy; + + /// If true, the 'Z' tensor is stored + static bool const kStoreZ = false; // We don't store anything in Z, + + /// If true, the 'T' tensor is stored + static bool const kStoreT = true; // this is our final output storage. + + /// Host-constructable parameters structure + struct Params { + FinalOp_ final_op_; + DistanceOp_ dist_op_; + + // + // Methods + // + CUTLASS_HOST_DEVICE + Params(DistanceOp_ dist_op, FinalOp final_op) : final_op_(final_op), dist_op_(dist_op) {} + + CUTLASS_HOST_DEVICE + Params() {} + }; + + private: + // + // Data members + // + FinalOp_ final_op; + DistanceOp_ elementwise_op; + + public: + // + // Methods + // + + /// Constructor from Params + CUTLASS_HOST_DEVICE + PairwiseDistanceEpilogueElementwise(Params const& params) + : final_op(params.final_op_), elementwise_op(params.dist_op_) + { + } + + /// Returns true if source is needed + CUTLASS_HOST_DEVICE + bool is_source_needed() const + { + // we use for making sure C matrix path is used for A mat norm. + return true; + } + + /// Functionally required for serial reduction in the epilogue + CUTLASS_HOST_DEVICE + void set_k_partition(int k_partition, int k_partition_count) {} + + /// Applies the operation when is_source_needed() is true + CUTLASS_HOST_DEVICE + void operator()(FragmentZ& frag_Z, + FragmentT& frag_T, + FragmentAccumulator const& AB, + FragmentC const& frag_C, + FragmentCompute const& V) const + { + FragmentCompute tmp_Accum = + NumericArrayConverter()(AB); + FragmentCompute tmp_C = + NumericArrayConverter()(frag_C); + FragmentCompute result_Z; + FragmentCompute result_T; + + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kElementsPerAccess; ++i) { + result_Z[i] = elementwise_op(tmp_C[i], V[i], tmp_Accum[i]); + result_T[i] = final_op(result_Z[i], 0); + } + + NumericArrayConverter convert_t; + frag_T = convert_t(result_T); + } + + /// Applies the operation when is_source_needed() is false + CUTLASS_HOST_DEVICE + void operator()(FragmentZ& frag_Z, + FragmentT& frag_T, + FragmentAccumulator const& AB, + FragmentCompute const& V) const + { + } +}; + +///////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace thread +} // namespace epilogue +} // namespace cutlass + +///////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/detail/pairwise_distance_gemm.h b/cpp/src/distance/detail/pairwise_distance_gemm.h new file mode 100644 index 0000000000..aaf2689dab --- /dev/null +++ b/cpp/src/distance/detail/pairwise_distance_gemm.h @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "./pairwise_distance_epilogue.h" + +#include +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { +namespace gemm { +namespace kernel { + +///////////////////////////////////////////////////////////////////////////////////////////////// + +template < + /// Element type for A matrix operand + typename ElementA_, + /// Layout type for A matrix operand + int kAlignmentA, + /// Element type for B matrix operand + typename ElementB_, + /// Layout type for B matrix operand + int kAlignmentB, + /// Element type for C and D matrix operands + typename ElementC_, + /// Element type for internal accumulation + typename ElementAccumulator, + /// Element type for final output + // typename ElementOutT, + /// Epilogue output operator - must satisfy concept of 'EpilogueWithBroadcastOp' + typename EpilogueOutputOp, + /// Number of stages used in the pipelined mainloop + int Stages, + /// data layout row/column major of inputs + bool isRowMajor> +struct PairwiseDistanceGemm { + // This struct is specialized for fp32/3xTF32 + + /// Threadblock-level tile size (concept: GemmShape) + using ThreadblockShape = + cutlass::gemm::GemmShape<128, 128, 16>; // <- threadblock tile M = 128, N = 128, K = 16 + /// Warp-level tile size (concept: GemmShape) + // This code section describes tile size a warp will compute + using WarpShape = cutlass::gemm::GemmShape<64, 64, 16>; // <- warp tile M = 64, N = 64, K = 16 + /// Warp-level tile size (concept: GemmShape) + // This code section describes the size of MMA op + using InstructionShape = + cutlass::gemm::GemmShape<16, 8, 4>; // <- MMA Op tile M = 16, N = 8, K = 4 + + /// Operation performed by GEMM + using Operator = cutlass::arch::OpMultiplyAddFastF32; + + // This code section describes whether you want to use tensor cores or regular SIMT cores on GPU + // SM + using OperatorClass = cutlass::arch::OpClassTensorOp; + + // This code section describes CUDA SM architecture number + using ArchTag = cutlass::arch::Sm80; + + // This code section describes how threadblocks are scheduled on GPU + /// Threadblock-level swizzling operator + using ThreadblockSwizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>; + + /// data layout for final output matrix. + // we keep this same layout even for column major inputs + using LayoutOutput = cutlass::layout::RowMajor; + + typedef typename std::conditional::type NormXLayout; + + typedef typename std:: + conditional::type LayoutA_; + + typedef typename std:: + conditional::type LayoutB_; + + using GemmBase = typename DefaultGemmUniversal::GemmKernel; + + // Replace epilogue + using Epilogue = typename cutlass::epilogue::threadblock::PairwiseDistanceEpilogue< + typename GemmBase::Epilogue::Shape, + typename GemmBase::Epilogue::WarpMmaOperator, + GemmBase::Epilogue::kPartitionsK, + ElementAccumulator, + typename EpilogueOutputOp::ElementT, + ElementAccumulator, + EpilogueOutputOp, + NormXLayout, + GemmBase::Epilogue::kElementsPerAccess>::Epilogue; + + // Compose the GEMM kernel + using GemmKernel = GemmWithFusedEpilogue; +}; + +template < + /// Layout type for A matrix operand + int kAlignmentA, + /// Layout type for B matrix operand + int kAlignmentB, + /// Element type for C and D matrix operands + typename ElementC_, + /// Element type for internal accumulation + typename ElementAccumulator, + /// Epilogue output operator - must satisfy concept of 'EpilogueWithBroadcastOp' + typename EpilogueOutputOp, + /// Number of stages used in the pipelined mainloop + int Stages, + /// data layout row/column major of inputs + bool isRowMajor> +struct PairwiseDistanceGemm { + // using Transform = cutlass::ComplexTransform::kNone; + // Threadblock-level tile size (concept: GemmShape) + using ThreadblockShape = + cutlass::gemm::GemmShape<64, 64, 16>; // <- threadblock tile M = 64, N = 64, K = 16 + /// Warp-level tile size (concept: GemmShape) + // This code section describes tile size a warp will compute + using WarpShape = cutlass::gemm::GemmShape<32, 32, 16>; // <- warp tile M = 32, N = 32, K = 16 + /// Warp-level tile size (concept: GemmShape) + // This code section describes the size of MMA op + using InstructionShape = cutlass::gemm::GemmShape<8, 8, 4>; + + // Operation performed by GEMM + using Operator = cutlass::arch::OpMultiplyAdd; + // This code section describes whether you want to use tensor cores or regular SIMT cores on GPU + // SM + using OperatorClass = cutlass::arch::OpClassTensorOp; + + // This code section describes CUDA SM architecture number + using ArchTag = cutlass::arch::Sm80; + + // This code section describes how threadblocks are scheduled on GPU + /// Threadblock-level swizzling operator + using ThreadblockSwizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>; + + /// data layout for final output matrix. + // we keep this same layout even for column major inputs + using LayoutOutput = cutlass::layout::RowMajor; + + typedef typename std::conditional::type NormXLayout; + + typedef typename std:: + conditional::type LayoutA_; + + typedef typename std:: + conditional::type LayoutB_; + + using GemmBase = typename DefaultGemmUniversal::GemmKernel; + + // Replace epilogue + using Epilogue = typename cutlass::epilogue::threadblock::PairwiseDistanceEpilogue< + typename GemmBase::Epilogue::Shape, + typename GemmBase::Epilogue::WarpMmaOperator, + GemmBase::Epilogue::kPartitionsK, + ElementC_, + typename EpilogueOutputOp::ElementT, + ElementC_, + EpilogueOutputOp, + NormXLayout, + GemmBase::Epilogue::kElementsPerAccess>::Epilogue; + + // Compose the GEMM kernel + using GemmKernel = GemmWithFusedEpilogue; +}; + +///////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace kernel +} // namespace gemm +} // namespace cutlass \ No newline at end of file diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch-ext.cuh b/cpp/src/distance/detail/pairwise_matrix/dispatch-ext.cuh new file mode 100644 index 0000000000..3e8402f1f9 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch-ext.cuh @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "../distance_ops/cutlass.cuh" // ops::has_cutlass_op +#include "../kernels/rbf_fin_op.cuh" // rbf_fin_op +#include "../pairwise_matrix/params.cuh" // pairwise_matrix_params +#include // raft::identity_op +#include // RAFT_EXPLICIT + +#ifdef RAFT_EXPLICIT_INSTANTIATE_ONLY + +namespace cuvs::distance::detail { + +template +void pairwise_matrix_dispatch(OpT distance_op, + IdxT m, + IdxT n, + IdxT k, + const DataT* x, + const DataT* y, + const DataT* x_norm, + const DataT* y_norm, + OutT* out, + FinOpT fin_op, + cudaStream_t stream, + bool is_row_major) RAFT_EXPLICIT; + +}; // namespace cuvs::distance::detail + +#endif // RAFT_EXPLICIT_INSTANTIATE_ONLY + +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + extern template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +/* + * Hierarchy of instantiations: + * + * This file defines extern template instantiations of the distance kernels. The + * instantiation of the public API is handled in raft/distance/distance-ext.cuh. + * + * After adding an instance here, make sure to also add the instance there. + */ + +// The following two instances are used in the RBF kernel object. Note the use of int64_t for the +// index type. +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, + float, + float, + float, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, + double, + double, + double, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); + +// Rest of instances +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::canberra_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::canberra_distance_op, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::correlation_distance_op, + float, + float, + float, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::correlation_distance_op, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::cosine_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::cosine_distance_op, double, double, double, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hamming_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hamming_distance_op, double, double, double, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hellinger_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hellinger_distance_op, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::jensen_shannon_distance_op, + float, + float, + float, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::jensen_shannon_distance_op, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::kl_divergence_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::kl_divergence_op, double, double, double, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l1_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l1_distance_op, double, double, double, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_exp_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_exp_distance_op, double, double, double, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l_inf_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l_inf_distance_op, double, double, double, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::lp_unexp_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::lp_unexp_distance_op, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::russel_rao_distance_op, float, float, float, raft::identity_op, int); +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::russel_rao_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch-inl.cuh b/cpp/src/distance/detail/pairwise_matrix/dispatch-inl.cuh new file mode 100644 index 0000000000..e64e9e5d7f --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch-inl.cuh @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +/* This file has two responsibilities: + * + * 1. Dispatch to the correct implementation of a kernel based on the + * architecture of the device on which the kernel will be launched. For + * instance, the cosine distance has a CUTLASS-based implementation that can + * be used on SM80+ and the normal implementation that is used on older + * architectures. + * + * 2. Provide concise function templates that can be instantiated in + * src/distance/detail/pairwise_matrix/. Previously, + * cuvs::distance::detail::distance was instantiated. The function + * necessarily required a large set of include files, which slowed down the + * build. The cuvs::distance::detail::pairwise_matrix_arch_dispatch functions + * do not require as large an include files set, which speeds up the build. + */ + +#include "../distance_ops/cutlass.cuh" // ops::has_cutlass_op +#include "../pairwise_matrix/dispatch_sm60.cuh" // dispatch_sm60 +#include "../pairwise_matrix/params.cuh" // pairwise_matrix_params +#include // raft::util::arch::SM_* + +// NOTE: to minimize compile times, we do not include dispatch_sm80.cuh. +// Including dispatch_sm80.cuh can slow down compile times (due to CUTLASS). +// Therefore, it is the including file's responsibility to include the correct +// dispatch_smXX.cuh headers, as is done in raft/distance/detail/distance.cuh +// and src/distance/detail/pairwise_matrix/dispatch_*.cu. + +namespace cuvs::distance::detail { + +// This forward-declaration ensures that we do not need to include +// dispatch_sm80.cuh if we are not calling it in practice. This makes compiling +// all the non-CUTLASS based distance instantiations faster. For CUTLASS-based +// distances, dispatch_sm80.cuh has to be included by the file including this +// file. +template +void pairwise_matrix_sm80_dispatch(OpT, + pairwise_matrix_params, + SM_compat_t, + cudaStream_t); + +template +void pairwise_matrix_dispatch(OpT distance_op, + IdxT m, + IdxT n, + IdxT k, + const DataT* x, + const DataT* y, + const DataT* x_norm, + const DataT* y_norm, + OutT* out, + FinOpT fin_op, + cudaStream_t stream, + bool is_row_major) +{ + // Create kernel parameter struct. Flip x and y if column major. + IdxT ldx = is_row_major ? k : m; + IdxT ldy = is_row_major ? k : n; + IdxT ld_out = is_row_major ? n : m; + + pairwise_matrix_params params{ + m, n, k, ldx, ldy, ld_out, x, y, x_norm, y_norm, out, fin_op, is_row_major}; + + if (!params.is_row_major) { params.flip_x_and_y(); } + + // Dispatch rule: + // - execute CUTLASS-based kernel on SM_80 and above + // - execute normal kernel below SM_80 + namespace arch = raft::util::arch; + + constexpr bool cutlass_op_unavailable = !ops::has_cutlass_op(); + + if constexpr (cutlass_op_unavailable) { + // Always execute legacy kernels when no cutlass op is available + auto any_range = arch::SM_range(arch::SM_min(), arch::SM_future()); + pairwise_matrix_sm60_dispatch(distance_op, params, any_range, stream); + } else { + auto cutlass_range = arch::SM_range(arch::SM_80(), arch::SM_future()); + auto legacy_range = arch::SM_range(arch::SM_min(), arch::SM_80()); + + // Get pointer to SM60 kernel to determine the best compute architecture + // out of all for which the kernel was compiled for that matches closely + // to the current device. Other methods to determine the architecture (that do not + // require a pointer) can be error prone. See: + // https://github.com/NVIDIA/cub/issues/545 + auto sm60_wrapper = pairwise_matrix_sm60_get_wrapper(distance_op, params, legacy_range); + void* kernel_ptr = reinterpret_cast(sm60_wrapper.kernel_ptr); + auto runtime_arch = arch::kernel_virtual_arch(kernel_ptr); + + if (cutlass_range.contains(runtime_arch)) { + // If device is SM_80 or later, use CUTLASS-based kernel. + pairwise_matrix_sm80_dispatch(distance_op, params, cutlass_range, stream); + } else { + // Reuse kernel wrapper that we obtained above. This avoids performing the + // dispatch twice. + sm60_wrapper.launch(distance_op, params, stream); + } + } +} + +}; // namespace cuvs::distance::detail diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch.cuh b/cpp/src/distance/detail/pairwise_matrix/dispatch.cuh new file mode 100644 index 0000000000..4a52b7ebe7 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch.cuh @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#ifndef RAFT_EXPLICIT_INSTANTIATE_ONLY +#include "dispatch-inl.cuh" +#endif + +#ifdef RAFT_COMPILED +#include "dispatch-ext.cuh" +#endif diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_00_generate.py b/cpp/src/distance/detail/pairwise_matrix/dispatch_00_generate.py new file mode 100644 index 0000000000..accd8de9bb --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_00_generate.py @@ -0,0 +1,194 @@ +# Copyright (c) 2023, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# NOTE: this template is not perfectly formatted. Use pre-commit to get +# everything in shape again. +header = """/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include // raft::identity_op +#include // ops::* +#include // dispatch +""" + + +macro = """ +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \\ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \\ + template void cuvs::distance::detail:: \\ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \\ + OpT distance_op, \\ + IdxT m, \\ + IdxT n, \\ + IdxT k, \\ + const DataT* x, \\ + const DataT* y, \\ + const DataT* x_norm, \\ + const DataT* y_norm, \\ + OutT* out, \\ + FinOpT fin_op, \\ + cudaStream_t stream, \\ + bool is_row_major) +""" + +data_type_instances = [ + dict( + DataT="float", + AccT="float", + OutT="float", + IdxT="int", + ), + dict( + DataT="double", + AccT="double", + OutT="double", + IdxT="int", + ), +] + +op_instances = [ + dict( + path_prefix="canberra", + OpT="cuvs::distance::detail::ops::canberra_distance_op", + archs = [60], + ), + dict( + path_prefix="correlation", + OpT="cuvs::distance::detail::ops::correlation_distance_op", + archs = [60], + ), + dict( + path_prefix="cosine", + OpT="cuvs::distance::detail::ops::cosine_distance_op", + archs = [60, 80], + ), + dict( + path_prefix="hamming_unexpanded", + OpT="cuvs::distance::detail::ops::hamming_distance_op", + archs = [60], + ), + dict( + path_prefix="hellinger_expanded", + OpT="cuvs::distance::detail::ops::hellinger_distance_op", + archs = [60], + ), + # inner product is handled by cublas. + dict( + path_prefix="jensen_shannon", + OpT="cuvs::distance::detail::ops::jensen_shannon_distance_op", + archs = [60], + ), + dict( + path_prefix="kl_divergence", + OpT="cuvs::distance::detail::ops::kl_divergence_op", + archs = [60], + ), + dict( + path_prefix="l1", + OpT="cuvs::distance::detail::ops::l1_distance_op", + archs = [60], + ), + dict( + path_prefix="l2_expanded", + OpT="cuvs::distance::detail::ops::l2_exp_distance_op", + archs = [60, 80], + ), + dict( + path_prefix="l2_unexpanded", + OpT="cuvs::distance::detail::ops::l2_unexp_distance_op", + archs = [60], + ), + dict( + path_prefix="l_inf", + OpT="cuvs::distance::detail::ops::l_inf_distance_op", + archs = [60], + ), + dict( + path_prefix="lp_unexpanded", + OpT="cuvs::distance::detail::ops::lp_unexp_distance_op", + archs = [60], + ), + dict( + path_prefix="russel_rao", + OpT="cuvs::distance::detail::ops::russel_rao_distance_op", + archs = [60], + ), +] + +def arch_headers(archs): + include_headers ="\n".join([ + f"#include " + for arch in archs + ]) + return include_headers + + + +for op in op_instances: + for dt in data_type_instances: + DataT, AccT, OutT, IdxT = (dt[k] for k in ["DataT", "AccT", "OutT", "IdxT"]); + path = f"dispatch_{op['path_prefix']}_{DataT}_{AccT}_{OutT}_{IdxT}.cu" + with open(path, "w") as f: + f.write(header) + f.write(arch_headers(op["archs"])) + f.write(macro) + + OpT = op['OpT'] + FinOpT = "raft::identity_op" + f.write(f"\ninstantiate_raft_distance_detail_pairwise_matrix_dispatch({OpT}, {DataT}, {AccT}, {OutT}, {FinOpT}, {IdxT});\n") + f.write("\n#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch\n") + print(f"src/distance/detail/pairwise_matrix/{path}") + +# Dispatch kernels for with the RBF fin op. +with open("dispatch_rbf.cu", "w") as f: + OpT="cuvs::distance::detail::ops::l2_unexp_distance_op" + archs = [60] + + f.write(header) + f.write("#include // rbf_fin_op\n") + f.write(arch_headers(archs)) + f.write(macro) + + for dt in data_type_instances: + DataT, AccT, OutT, IdxT = (dt[k] for k in ["DataT", "AccT", "OutT", "IdxT"]); + IdxT = "int64_t" # overwrite IdxT + + FinOpT = f"cuvs::distance::kernels::detail::rbf_fin_op<{DataT}>" + f.write(f"\ninstantiate_raft_distance_detail_pairwise_matrix_dispatch({OpT}, {DataT}, {AccT}, {OutT}, {FinOpT}, {IdxT});\n") + + f.write("\n#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch\n") + +print("src/distance/detail/pairwise_matrix/dispatch_rbf.cu") diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_double_double_double_int.cu new file mode 100644 index 0000000000..f82df6cc03 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_double_double_double_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::canberra_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_float_float_float_int.cu new file mode 100644 index 0000000000..a20ca5f472 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_canberra_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::canberra_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_double_double_double_int.cu new file mode 100644 index 0000000000..7bb7e4a96e --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_double_double_double_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::correlation_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_float_float_float_int.cu new file mode 100644 index 0000000000..34fcc4be42 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_correlation_float_float_float_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::correlation_distance_op, + float, + float, + float, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_double_double_double_int.cu new file mode 100644 index 0000000000..cb23743c1f --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_double_double_double_int.cu @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include "dispatch_sm80.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::cosine_distance_op, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_float_float_float_int.cu new file mode 100644 index 0000000000..ad71ff2957 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_cosine_float_float_float_int.cu @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include "dispatch_sm80.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::cosine_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_double_double_double_int.cu new file mode 100644 index 0000000000..e81d544112 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_double_double_double_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hamming_distance_op, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_float_float_float_int.cu new file mode 100644 index 0000000000..ddbdab6021 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_hamming_unexpanded_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hamming_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_double_double_double_int.cu new file mode 100644 index 0000000000..d2acecaf0e --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_double_double_double_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hellinger_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_float_float_float_int.cu new file mode 100644 index 0000000000..034d76679e --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_hellinger_expanded_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::hellinger_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_double_double_double_int.cu new file mode 100644 index 0000000000..030faeecd2 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_double_double_double_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::jensen_shannon_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_float_float_float_int.cu new file mode 100644 index 0000000000..f7551a5665 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_jensen_shannon_float_float_float_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::jensen_shannon_distance_op, + float, + float, + float, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_double_double_double_int.cu new file mode 100644 index 0000000000..6640d39492 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_double_double_double_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::kl_divergence_op, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_float_float_float_int.cu new file mode 100644 index 0000000000..60cafa4742 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_kl_divergence_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::kl_divergence_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l1_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l1_double_double_double_int.cu new file mode 100644 index 0000000000..b5e8a2f687 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l1_double_double_double_int.cu @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op + +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l1_distance_op, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l1_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l1_float_float_float_int.cu new file mode 100644 index 0000000000..73868a486b --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l1_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l1_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_double_double_double_int.cu new file mode 100644 index 0000000000..8ac80b77d2 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_double_double_double_int.cu @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include "dispatch_sm80.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_exp_distance_op, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_float_float_float_int.cu new file mode 100644 index 0000000000..abebb91219 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_expanded_float_float_float_int.cu @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include "dispatch_sm80.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_exp_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_double_double_double_int.cu new file mode 100644 index 0000000000..ffa6bf02ba --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_double_double_double_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_float_float_float_int.cu new file mode 100644 index 0000000000..acef42a4e0 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l2_unexpanded_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_double_double_double_int.cu new file mode 100644 index 0000000000..c2bbbf06b5 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_double_double_double_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l_inf_distance_op, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_float_float_float_int.cu new file mode 100644 index 0000000000..163b9f37ba --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_l_inf_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l_inf_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_layout.cuh b/cpp/src/distance/detail/pairwise_matrix/dispatch_layout.cuh new file mode 100644 index 0000000000..1f95e8e41e --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_layout.cuh @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "params.cuh" // pairwise_matrix_params +#include // RAFT_EXPECTS + +#include // std::min +#include // size_t +#include // std::integral_constant +namespace cuvs::distance::detail { + +/** + * @brief: Computes minimal common alignment of the rows in a 2D array in bytes + * + * The 2D matrix `x` is assumed to be row-major. This function computes the + * minimal alignment in bytes of the first elements of each row. + * Output can be 16, 8, 4, 2, 1. + * + * @param x Base pointer of row-major input matrix + * @param stride Stride in number of element between consecutive rows. + */ +template +size_t alignment_of_2d_array(const DataT* x, size_t stride) +{ + auto base = reinterpret_cast(x); + size_t stride_bytes = sizeof(DataT) * stride; + + for (int align = 16; align >= 0; align /= 2) { + bool base_aligned = base % align == 0; + bool stride_aligned = stride_bytes % align == 0; + if (base_aligned && stride_aligned) { return align; } + } + return 1; +} + +/** + * @brief: Computes the vec_len parameter kernel policy parameter + * + * @param params Kernel parameters + */ +template +int determine_vec_len(pairwise_matrix_params params) +{ + size_t align_x = alignment_of_2d_array(params.x, params.ldx); + size_t align_y = alignment_of_2d_array(params.y, params.ldy); + size_t byte_alignment = min(align_x, align_y); + + // Since alignment is in bytes, it could be smaller than sizeof(DataT). + // Handle this (unlikely) case here. + RAFT_EXPECTS(sizeof(DataT) <= byte_alignment, + "Input matrix must be aligned to size of elements."); + + // Compute number of elements that can be loaded in one instruction + // without causing misalignent errors. + int vec_len_aligned = (byte_alignment % sizeof(DataT) == 0) ? byte_alignment / sizeof(DataT) : 1; + + // In the future, pairwise_matrix might support `int8_t` input. In that case, + // byte_alignment / sizeof(DataT) might exceed 4. We maximize at 4 here, to + // prevent adding more cases in dispatch_layout below (which are expensive to + // compile). + vec_len_aligned = std::min(vec_len_aligned, 4); + + return vec_len_aligned; +} + +template +using vec_len_constant = std::integral_constant; + +/** + * @brief: Converts run-time arguments to compile-time arguments + * + * Converts run-time arguments row_major and vec_len to compile-time arguments + * and dispatches a lambda f with these compile-time arguments. + * + * This is equivalent to copying and pasting the lambda function `f` in each of + * the switch case statements. + * + * @tparam F Type of lambda f. + * @param row_major Boolean indicating whether input arrays have row-major layout. + * @param vec_len Integer value 1, 2, or 4 specifying the Veclen template parameter of + * the KernelPolicy. + * @param f Lambda that takes two std::integral_constant parameters representing + * row_major and vec_len. + */ +template +auto dispatch_layout(bool row_major, int vec_len, F&& f) +{ + if (row_major) { + switch (vec_len) { + case 4: return f(std::true_type(), vec_len_constant<4>()); + case 2: return f(std::true_type(), vec_len_constant<2>()); + default: return f(std::true_type(), vec_len_constant<1>()); + } + } else { + switch (vec_len) { + case 4: return f(std::false_type(), vec_len_constant<4>()); + case 2: return f(std::false_type(), vec_len_constant<2>()); + default: return f(std::false_type(), vec_len_constant<1>()); + } + } +} + +}; // namespace cuvs::distance::detail diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_double_double_double_int.cu new file mode 100644 index 0000000000..d13532ac60 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_double_double_double_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::lp_unexp_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_float_float_float_int.cu new file mode 100644 index 0000000000..65e0163d78 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_lp_unexpanded_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::lp_unexp_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_rbf.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_rbf.cu new file mode 100644 index 0000000000..23f2b34e8c --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_rbf.cu @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "../kernels/rbf_fin_op.cuh" // rbf_fin_op +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, + float, + float, + float, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::l2_unexp_distance_op, + double, + double, + double, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_double_double_double_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_double_double_double_int.cu new file mode 100644 index 0000000000..1a5e5cf981 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_double_double_double_int.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::russel_rao_distance_op, + double, + double, + double, + raft::identity_op, + int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_float_float_float_int.cu b/cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_float_float_float_int.cu new file mode 100644 index 0000000000..a9b1f6bb43 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_russel_rao_float_float_float_int.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by dispatch_00_generate.py + * + * Make changes there and run in this directory: + * + * > python dispatch_00_generate.py + * + */ + +#include "../distance_ops/all_ops.cuh" // ops::* +#include "dispatch-inl.cuh" // dispatch +#include "dispatch_sm60.cuh" +#include // raft::identity_op +#define instantiate_raft_distance_detail_pairwise_matrix_dispatch( \ + OpT, DataT, AccT, OutT, FinOpT, IdxT) \ + template void cuvs::distance::detail:: \ + pairwise_matrix_dispatch, DataT, AccT, OutT, FinOpT, IdxT>( \ + OpT distance_op, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + const DataT* x, \ + const DataT* y, \ + const DataT* x_norm, \ + const DataT* y_norm, \ + OutT* out, \ + FinOpT fin_op, \ + cudaStream_t stream, \ + bool is_row_major) + +instantiate_raft_distance_detail_pairwise_matrix_dispatch( + cuvs::distance::detail::ops::russel_rao_distance_op, float, float, float, raft::identity_op, int); + +#undef instantiate_raft_distance_detail_pairwise_matrix_dispatch diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_sm60.cuh b/cpp/src/distance/detail/pairwise_matrix/dispatch_sm60.cuh new file mode 100644 index 0000000000..2b0ed01ef5 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_sm60.cuh @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "dispatch_layout.cuh" // dispatch_layout +#include "kernel_sm60.cuh" // pairwise_matrix_sm60_wrapper +#include // raft::linalg::Policy4x4 + +#include // std::min + +namespace cuvs::distance::detail { + +template +pairwise_matrix_sm60_wrapper pairwise_matrix_sm60_get_wrapper( + OpT distance_op, + pairwise_matrix_params params, + SM_compat_t sm_compat_range) +{ + int vec_len = determine_vec_len(params); + + // f takes compile-time constants row_major and vec_len aligned and returns + // the corresponding kernel wrapper. The wrapper contains the launch + // parameters of the kernel: a pointer to the kernel function, grid size, + // block size, and shared memory size. + auto f = [&](auto row_major, auto vec_len_aligned) { + // row_major and vec_len are std::integral_constants of type bool and int + // respectively. + + // To keep compile times in check, we only specialize on veclen > 1 when + // the inner loop is relatively cheap (< 5 flops). + constexpr int vec_len_op = distance_op.expensive_inner_loop ? 1 : vec_len_aligned(); + + // Prevent double, vec_len=4 combination (this is not supported) + constexpr int vec_len = std::min(vec_len_op, static_cast(16 / sizeof(DataT))); + + using RowPolicy = typename raft::linalg::Policy4x4::Policy; + using ColPolicy = typename raft::linalg::Policy4x4::ColPolicy; + using Policy = typename std::conditional::type; + + auto wrapper = + make_pairwise_matrix_sm60_wrapper(distance_op, params, sm_compat_range); + + return wrapper; + }; + + // Dispatch_layout calls f with appropriate compile time constants based on + // the runtime values of params.is_row_major and vec_len. + return dispatch_layout(params.is_row_major, vec_len, f); +} + +template +void pairwise_matrix_sm60_dispatch(OpT distance_op, + pairwise_matrix_params params, + SM_compat_t sm_compat_range, + cudaStream_t stream) +{ + auto wrapper = pairwise_matrix_sm60_get_wrapper(distance_op, params, sm_compat_range); + + wrapper.launch(distance_op, params, stream); +} + +} // namespace cuvs::distance::detail diff --git a/cpp/src/distance/detail/pairwise_matrix/dispatch_sm80.cuh b/cpp/src/distance/detail/pairwise_matrix/dispatch_sm80.cuh new file mode 100644 index 0000000000..d9761545e3 --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/dispatch_sm80.cuh @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "../pairwise_distance_cutlass_base.cuh" // cutlassDistanceKernel +#include "dispatch_layout.cuh" // dispatch_layout + +#include // std::min + +namespace cuvs::distance::detail { + +template +void pairwise_matrix_sm80_dispatch(OpT distance_op, + pairwise_matrix_params params, + SM_compat_t sm_compat_range, + cudaStream_t stream) +{ + int vec_len = determine_vec_len(params); + + // f takes compile-time constants row_major and vec_len aligned and runs the + // corresponding cutlass launch code. + auto f = [&](auto row_major, auto vec_len_aligned) { + // row_major and vec_len are std::integral_constants of type bool and int + // respectively. + + // Prevent double, vec_len=4 combination (this is not supported) + constexpr int vec_len = std::min(vec_len_aligned(), static_cast(16 / sizeof(DataT))); + + using AccT = typename OpT::AccT; + cutlassDistanceKernel(params.x, + params.y, + params.x_norm, + params.y_norm, + params.m, + params.n, + params.k, + params.ldx, + params.ldy, + params.ld_out, + params.out, + params.fin_op, + distance_op, + stream); + }; + + // Dispatch_layout calls f with appropriate compile time constants based on + // the runtime values of params.is_row_major and vec_len. + dispatch_layout(params.is_row_major, vec_len, f); +} + +}; // namespace cuvs::distance::detail diff --git a/cpp/src/distance/detail/pairwise_matrix/kernel_sm60.cuh b/cpp/src/distance/detail/pairwise_matrix/kernel_sm60.cuh new file mode 100644 index 0000000000..b63955422d --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/kernel_sm60.cuh @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "../pairwise_distance_base.cuh" // PairwiseDistances +#include "params.cuh" // pairwise_matrix_params +#include // raft::void_op +#include // raft::util::arch::SM_compute_arch + +#include // assert + +namespace cuvs::distance::detail { + +template +__launch_bounds__(Policy::Nthreads, 2) RAFT_KERNEL + pairwise_matrix_kernel(OpT distance_op, pairwise_matrix_params params) +{ + // Early exit to minimize the size of the kernel when it is not supposed to be compiled. + constexpr SM_compat_t sm_compat_range{}; + if constexpr (!sm_compat_range.contains(raft::util::arch::SM_compute_arch())) { + assert(false); + return; + } + + extern __shared__ char smem[]; + + // The epilog is already provided by distance_op. Do not provide additional + // epilogs. + auto epilog_op = raft::void_op(); + // No support for row_epilog_op. + auto row_epilog_op = raft::void_op(); + + // Always write output + constexpr bool write_out = true; + constexpr bool use_norms = distance_op.use_norms; + PairwiseDistances + obj(params.x, + params.y, + params.m, + params.n, + params.k, + params.ldx, + params.ldy, + params.ld_out, + params.x_norm, + params.y_norm, + params.out, + smem, + distance_op, + epilog_op, + params.fin_op, + row_epilog_op); + obj.run(); +} + +// The type of a pointer to the pairwise matrix kernel. The following template +// arguments are type-erased: +// +// - The kernel policy +// - row_major +// - SM_compat_t +template +using pairwise_matrix_kernel_t = void (*)(OpT, pairwise_matrix_params); + +// A wrapper for the pairwise matrix kernel launch. Includes kernel launch +// parameters. +template +struct pairwise_matrix_sm60_wrapper { + dim3 grid; + dim3 block; + int smem_size; + pairwise_matrix_kernel_t kernel_ptr; + + void launch(OpT distance_op, + pairwise_matrix_params params, + cudaStream_t stream) + { + kernel_ptr<<>>(distance_op, params); + RAFT_CUDA_TRY(cudaGetLastError()); + } +}; + +/** @brief: Create kernel launch wrapper for pairwise matrix kernel + * + * This can be used to type-erase the kernel execution policy, row_major, and SM + * compatibility range. + * + * @tparam Policy: Kernel execution policy + * @tparam row_major: Indicates whether input matrices are row major + * @tparam OpT: Type of distance operation + * @tparam IdxT: Index type + * @tparam DataT: Data type + * @tparam OutT: Output data type + * @tparam FinOpT: Final operation type + * @tparam SM_compat_t: Type of the SM architecture compatibility + * + * @param distance_op: Distance operation + * @param params: Parameters + * @param sm_compat_range: Which SM architectures to compile for. + */ +template +pairwise_matrix_sm60_wrapper make_pairwise_matrix_sm60_wrapper( + OpT distance_op, + pairwise_matrix_params params, + SM_compat_t sm_compat_range) +{ + dim3 block(Policy::Nthreads); + // Use ::template to disambiguate (See: + // https://en.cppreference.com/w/cpp/language/dependent_name) + int smem_size = OpT::template shared_mem_size(); + // Obtain function pointer to kernel + auto kernel = + pairwise_matrix_kernel; + dim3 grid = launchConfigGenerator(params.m, params.n, smem_size, kernel); + + return pairwise_matrix_sm60_wrapper{ + grid, block, smem_size, kernel}; +} + +}; // namespace cuvs::distance::detail diff --git a/cpp/src/distance/detail/pairwise_matrix/params.cuh b/cpp/src/distance/detail/pairwise_matrix/params.cuh new file mode 100644 index 0000000000..aa419aca0d --- /dev/null +++ b/cpp/src/distance/detail/pairwise_matrix/params.cuh @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +namespace cuvs::distance::detail { + +template +struct pairwise_matrix_params { + IdxT m; + IdxT n; + IdxT k; + IdxT ldx; + IdxT ldy; + IdxT ld_out; + const DataT* x; + const DataT* y; + const DataT* x_norm; + const DataT* y_norm; + OutT* out; + FinOpT fin_op; + bool is_row_major; + + /// @brief: Flips the x and y input and corresponding sizes + void flip_x_and_y() + { + // Flip m, n; ldx, ldy; x, y; x_norm, y_norm. + std::swap(m, n); + std::swap(ldx, ldy); + std::swap(x, y); + std::swap(x_norm, y_norm); + } +}; + +} // namespace cuvs::distance::detail diff --git a/cpp/src/distance/detail/predicated_tile_iterator_normvec.h b/cpp/src/distance/detail/predicated_tile_iterator_normvec.h new file mode 100644 index 0000000000..951f8a0132 --- /dev/null +++ b/cpp/src/distance/detail/predicated_tile_iterator_normvec.h @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! \file + \brief Epilogue for threadblock scoped GEMMs using Tensor Ops. + +This file contains a customized version of PredicatedTileIterator from CUTLASS 2.9.0 +(https://github.com/NVIDIA/cutlass/blob/v2.9.0/include/cutlass/epilogue/threadblock/predicated_tile_iterator.h#L75) + +Changes: +- added `Layout_` template param +- Only the row index is used to load the data in load_with_byte_offset(). + This way the same normalization data is used across all columns in a row. + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +namespace cutlass { + +//////////////////////////////////////////////////////////////////////////////// + +namespace epilogue { +namespace threadblock { + +//////////////////////////////////////////////////////////////////////////////// + +/// Tile iterator used to load and store output tile from global memory in epilogue. +/// +/// Satisfies: ReadableTileIterator | PredicatedTileIterator | ForwardTileIterator +/// +template +class PredicatedTileIteratorNormVec { + public: + using ThreadMap = ThreadMap_; + using Shape = typename ThreadMap::Shape; + + using Element = Element_; + + using Layout = Layout_; + using TensorRef = TensorRef; + using ConstTensorRef = typename TensorRef::ConstTensorRef; + + using Index = typename Layout::Index; + using LongIndex = typename Layout::LongIndex; + using TensorCoord = MatrixCoord; + + static int const kElementsPerAccess = ThreadMap::kElementsPerAccess; + static int const kThreads = ThreadMap::kThreads; + static int const kIterations = ThreadMap::Count::kTile; + + static_assert(ThreadMap::Iterations::kRow > 0, "ThreadMap::Iterations::kRow must be > 0"); + static_assert(ThreadMap::Iterations::kGroup > 0, "ThreadMap::Iterations::kGroup must be > 0"); + static_assert(ThreadMap::Iterations::kCluster > 0, "ThreadMap::Iterations::kCluster must be > 0"); + static_assert(ThreadMap::Iterations::kColumn > 0, "ThreadMap::Iterations::kColumn must be > 0"); + + /// Fragment object + using Fragment = Array; + + /// Memory access size + using AccessType = AlignedArray; + + // + // Parameters struct + // + + /// Uses a non-template class + struct Params : PredicatedTileIteratorParams { + using Base = PredicatedTileIteratorParams; + + CUTLASS_HOST_DEVICE + Params() {} + + CUTLASS_HOST_DEVICE + Params(Layout const& layout) + : PredicatedTileIteratorParams( + layout.stride(0) * int(sizeof(AccessType)) / kElementsPerAccess, + make_OutputTileThreadMapDesc()) + { + } + + CUTLASS_HOST_DEVICE + Params(Base const& base) : Base(base) {} + }; + + /// Mask object + struct Mask { + static int const kCount = ThreadMap::Iterations::kColumn; + + /// Predicate state + bool predicates[kCount]; + + // + // Mask + // + CUTLASS_HOST_DEVICE + Mask() { enable(); } + + ///< Efficiently disables all accesses guarded by mask + CUTLASS_HOST_DEVICE void clear() + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kCount; ++i) { + predicates[i] = false; + } + } + + ///< CUTLASS_HOST_DEVICE enables all accesses guarded by mask + CUTLASS_DEVICE void enable() + { + CUTLASS_PRAGMA_UNROLL + for (int i = 0; i < kCount; ++i) { + predicates[i] = true; + } + } + }; + + private: + // + // Data members + // + + /// Parameters structure containing reference and precomputed state. + PredicatedTileIteratorParams params_; + + /// Byte-level pointer + uint8_t* byte_pointer_; + + /// Array of boolean values to contain steady-state predicates + Mask mask_; + + /// Extent of the matrix tile in rows + Index extent_row_; + + /// Extent of the matrix tile in rows + Index extent_column_; + + /// A thread's starting row position (assuming steady-state predicates have been computed) + Index thread_start_row_; + + /// A thread's starting column + Index thread_start_column_; + + /// Internal state counter + int state_[3]; + + /// Scatter indices + int const* indices_; + + // + // Static asserts about internal strides + // + + static_assert(sizeof(extent_row_) == 4, "Expected 32b extents"); + static_assert(sizeof(thread_start_row_) == 4, "Expected 32b extents"); + static_assert(sizeof(PredicatedTileIteratorParams::stride) == 8, "Expected 64b strides"); + + private: + // + // Methods + // + + public: + // + // Methods + // + + /// Constructor + CUTLASS_DEVICE + PredicatedTileIteratorNormVec(PredicatedTileIteratorParams const& params, + Element* pointer, + TensorCoord extent, + int thread_idx, + TensorCoord threadblock_offset = TensorCoord(), + int const* indices = nullptr) + : params_(params), indices_(indices) + { + TensorCoord thread_offset = ThreadMap::initial_offset(thread_idx) + threadblock_offset; + + extent_row_ = extent.row(); + extent_column_ = extent.column(); + + thread_start_row_ = thread_offset.row(); + thread_start_column_ = thread_offset.column(); + + // Initialize predicates + CUTLASS_PRAGMA_UNROLL + for (int c = 0; c < ThreadMap::Iterations::kColumn; ++c) { + mask_.predicates[c] = + ((thread_offset.column() + ThreadMap::Delta::kColumn * c) < extent.column()); + } + + // Null pointer performs no accesses + if (!pointer) { mask_.clear(); } + + if (ScatterD && !indices) { mask_.clear(); } + + // Initialize pointer + byte_pointer_ = reinterpret_cast(pointer) + + LongIndex(thread_offset.row()) * LongIndex(params_.stride); + + if (ScatterD) { + byte_pointer_ = reinterpret_cast(pointer) + + LongIndex(thread_offset.column()) * sizeof(AccessType) / kElementsPerAccess; + } + + // Initialize internal state counter + state_[0] = state_[1] = state_[2] = 0; + } + + /// Adds a pointer offset in units of Element + CUTLASS_HOST_DEVICE + void add_pointer_offset(LongIndex pointer_offset) + { + byte_pointer_ += pointer_offset * sizeof_bits::value / 8; + } + + /// Loads a fragment from memory + CUTLASS_DEVICE + void load_with_byte_offset(Fragment& frag, int64_t byte_offset) const + { + uint8_t* byte_pointer = byte_pointer_; + AccessType* frag_ptr = reinterpret_cast(&frag); + + CUTLASS_PRAGMA_UNROLL + for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { + CUTLASS_PRAGMA_UNROLL + for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { + CUTLASS_PRAGMA_UNROLL + for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { + int frag_row_idx = + (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); + + int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + + cluster * ThreadMap::Delta::kCluster; + + bool row_guard = ((row_offset + thread_start_row_) < extent_row_); + + AccessType* memory_pointer = reinterpret_cast(byte_pointer + byte_offset); + + if (ScatterD && row_guard) { + assert(indices_); + + memory_pointer = reinterpret_cast( + byte_pointer + byte_offset + + LongIndex(indices_[row_offset + thread_start_row_]) * LongIndex(params_.stride)); + } + + CUTLASS_PRAGMA_UNROLL + for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { + bool guard = row_guard && mask_.predicates[column]; + if (column == 0) { + cutlass::arch::global_load( + frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], + (void*)&memory_pointer[0], + guard); + } else { + frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column] = + frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn]; + } + } + + if (row + 1 < ThreadMap::Iterations::kRow) { + if (!ScatterD) { byte_pointer += params_.increment_row; } + } + } + + if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } + } + + if (cluster + 1 < ThreadMap::Iterations::kCluster) { + byte_pointer += params_.increment_cluster; + } + } + } + + /// Loads a fragment from memory + CUTLASS_DEVICE + void load(Fragment& frag) const { load_with_byte_offset(frag, 0); } + + /// Stores a fragment to memory + CUTLASS_DEVICE + void store_with_byte_offset(Fragment const& frag, int64_t byte_offset) const + { + uint8_t* byte_pointer = byte_pointer_; + AccessType const* frag_ptr = reinterpret_cast(&frag); + + CUTLASS_PRAGMA_UNROLL + for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { + CUTLASS_PRAGMA_UNROLL + for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { + CUTLASS_PRAGMA_UNROLL + for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { + int frag_row_idx = + (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); + + int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + + cluster * ThreadMap::Delta::kCluster; + + bool row_guard = ((row_offset + thread_start_row_) < extent_row_); + + AccessType* memory_pointer = reinterpret_cast(byte_pointer + byte_offset); + + if (ScatterD && row_guard) { + assert(indices_); + + memory_pointer = reinterpret_cast( + byte_pointer + byte_offset + + LongIndex(indices_[row_offset + thread_start_row_]) * LongIndex(params_.stride)); + } + + CUTLASS_PRAGMA_UNROLL + for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { + bool guard = row_guard && mask_.predicates[column]; + + if (UseCUDAStore) { + if (guard) { + memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess] = + frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column]; + } + } else { + cutlass::arch::global_store( + frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], + (void*)&memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess], + guard); + } + } + + if (row + 1 < ThreadMap::Iterations::kRow) { + if (!ScatterD) { byte_pointer += params_.increment_row; } + } + } + + if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } + } + + if (cluster + 1 < ThreadMap::Iterations::kCluster) { + byte_pointer += params_.increment_cluster; + } + } + } + + /// Stores a fragment to memory + CUTLASS_DEVICE + void store(Fragment const& frag) const { store_with_byte_offset(frag, 0); } + + /// Loads a fragment from memory + CUTLASS_DEVICE + void downsample_load_with_byte_offset(Fragment& frag, + int64_t byte_offset, + int convolution_P, + int convolution_Q, + int add_P, + int add_Q, + int problem_N) const + { + uint8_t* byte_pointer = byte_pointer_; + AccessType* frag_ptr = reinterpret_cast(&frag); + + CUTLASS_PRAGMA_UNROLL + for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { + CUTLASS_PRAGMA_UNROLL + for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { + CUTLASS_PRAGMA_UNROLL + for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { + int frag_row_idx = + (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); + + int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + + cluster * ThreadMap::Delta::kCluster; + + bool row_guard = ((row_offset + thread_start_row_) < extent_row_); + + int output_row = row_offset + thread_start_row_; + int output_N = output_row / (convolution_P * convolution_Q); + int output_PQ = output_row % (convolution_P * convolution_Q); + int output_P = output_PQ / convolution_Q; + int output_Q = output_PQ % convolution_Q; + + int input_row = output_N * 2 * convolution_P * 2 * convolution_Q + + (2 * output_P + add_P) * 2 * convolution_Q + 2 * output_Q + add_Q; + + int64_t byte_offset = (input_row - output_row) * problem_N * sizeof(float); + + AccessType* memory_pointer = reinterpret_cast(byte_pointer + byte_offset); + + CUTLASS_PRAGMA_UNROLL + for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { + bool guard = row_guard && mask_.predicates[column]; + + cutlass::arch::global_load( + frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], + (void*)&memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess], + guard); + } + + if (row + 1 < ThreadMap::Iterations::kRow) { byte_pointer += params_.increment_row; } + } + + if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } + } + + if (cluster + 1 < ThreadMap::Iterations::kCluster) { + byte_pointer += params_.increment_cluster; + } + } + } + + /// Loads a fragment from memory + CUTLASS_DEVICE + void upsample_load_with_byte_offset(Fragment& frag, + int64_t byte_offset, + int convolution_P, + int convolution_Q, + int add_P, + int add_Q, + int problem_N) const + { + uint8_t* byte_pointer = byte_pointer_; + AccessType* frag_ptr = reinterpret_cast(&frag); + + CUTLASS_PRAGMA_UNROLL + for (int cluster = 0; cluster < ThreadMap::Iterations::kCluster; ++cluster) { + CUTLASS_PRAGMA_UNROLL + for (int group = 0; group < ThreadMap::Iterations::kGroup; ++group) { + CUTLASS_PRAGMA_UNROLL + for (int row = 0; row < ThreadMap::Iterations::kRow; ++row) { + int frag_row_idx = + (row + ThreadMap::Iterations::kRow * (group + ThreadMap::Iterations::kGroup * cluster)); + + int row_offset = row * ThreadMap::Delta::kRow + group * ThreadMap::Delta::kGroup + + cluster * ThreadMap::Delta::kCluster; + + bool row_guard = ((row_offset + thread_start_row_) < extent_row_); + + int output_row = row_offset + thread_start_row_; + int output_N = output_row / (convolution_P * convolution_Q); + int output_PQ = output_row % (convolution_P * convolution_Q); + int output_P = output_PQ / convolution_Q; + int output_Q = output_PQ % convolution_Q; + int row_add_P = add_P; + int row_add_Q = add_Q; + if (output_P > convolution_P - 2) row_add_P = 0; + if (output_Q > convolution_Q - 2) row_add_Q = 0; + + int input_row = output_N * (convolution_P / 2) * (convolution_Q / 2) + + ((output_P + row_add_P) / 2) * (convolution_Q / 2) + + (output_Q + row_add_Q) / 2; + + int64_t byte_offset = (input_row - output_row) * problem_N * sizeof(float); + + AccessType* memory_pointer = reinterpret_cast(byte_pointer + byte_offset); + + CUTLASS_PRAGMA_UNROLL + for (int column = 0; column < ThreadMap::Iterations::kColumn; ++column) { + bool guard = row_guard && mask_.predicates[column]; + + cutlass::arch::global_load( + frag_ptr[frag_row_idx * ThreadMap::Iterations::kColumn + column], + (void*)&memory_pointer[column * ThreadMap::Delta::kColumn / kElementsPerAccess], + guard); + } + + if (row + 1 < ThreadMap::Iterations::kRow) { byte_pointer += params_.increment_row; } + } + + if (group + 1 < ThreadMap::Iterations::kGroup) { byte_pointer += params_.increment_group; } + } + + if (cluster + 1 < ThreadMap::Iterations::kCluster) { + byte_pointer += params_.increment_cluster; + } + } + } + + CUTLASS_DEVICE + MatrixCoord thread_start() const { return MatrixCoord(thread_start_row_, thread_start_column_); } + + /// Need to get the thread start row from the tile iterator + CUTLASS_DEVICE + int32_t thread_start_row() const { return thread_start_row_; } + + /// Need to get the thread start row from the tile iterator + CUTLASS_DEVICE + int32_t thread_start_column() const { return thread_start_column_; } + + /// Extent of the matrix in rows + CUTLASS_DEVICE + Index extent_row() const { return extent_row_; } + + /// Extent of the matrix in columns + CUTLASS_DEVICE + Index extent_column() const { return extent_column_; } + + /// Advances to the next position to load or store + CUTLASS_HOST_DEVICE + PredicatedTileIteratorNormVec& operator++() + { + ++state_[0]; + + if (!ScatterD) { byte_pointer_ += params_.advance_row; } + + thread_start_row_ += ThreadMap::Shape::kRow; + + if (state_[0] == ThreadMap::Count::kRow) { + state_[0] = 0; + ++state_[1]; + byte_pointer_ += params_.advance_group; + + thread_start_row_ += + (ThreadMap::Shape::kGroup - 1) * ThreadMap::Shape::kRow * ThreadMap::Count::kRow; + + if (state_[1] == ThreadMap::Count::kGroup) { + state_[1] = 0; + ++state_[2]; + byte_pointer_ += params_.advance_cluster; + + thread_start_row_ += ThreadMap::Count::kGroup * ThreadMap::Shape::kGroup * + ThreadMap::Count::kRow * ThreadMap::Shape::kRow; + + if (state_[2] == ThreadMap::Count::kCluster) { + state_[2] = 0; + byte_pointer_ += params_.advance_tile; + } + } + } + + return *this; + } + + ///< Efficiently disables all accesses guarded by mask + CUTLASS_DEVICE void clear_mask() { mask_.clear(); } + + ///< Efficiently enables all accesses guarded by mask + CUTLASS_DEVICE void enable_mask() { mask_.enable(); } + + ///< Sets the mask + CUTLASS_DEVICE void get_mask(Mask& mask) const { mask = mask_; } + + ///< Sets the mask + CUTLASS_DEVICE void set_mask(Mask const& mask) { mask_ = mask; } +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace threadblock +} // namespace epilogue +} // namespace cutlass + +//////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/src/distance/distance-ext.cuh b/cpp/src/distance/distance-ext.cuh new file mode 100644 index 0000000000..ad45e84059 --- /dev/null +++ b/cpp/src/distance/distance-ext.cuh @@ -0,0 +1,1066 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "detail/kernels/rbf_fin_op.cuh" // rbf_fin_op +#include // cuvs::distance::DistanceType +#include // raft::device_matrix_view +#include // raft::identity_op +#include // raft::resources +#include // RAFT_EXPLICIT + +#include // rmm::device_uvector + +#ifdef RAFT_EXPLICIT_INSTANTIATE_ONLY + +namespace cuvs { +namespace distance { + +template +void distance(raft::resources const& handle, + const DataT* x, + const DataT* y, + OutT* dist, + IdxT m, + IdxT n, + IdxT k, + void* workspace, + size_t worksize, + FinalLambda fin_op, + bool isRowMajor = true, + DataT metric_arg = 2.0f) RAFT_EXPLICIT; + +template +void distance(raft::resources const& handle, + const DataT* x, + const DataT* y, + OutT* dist, + IdxT m, + IdxT n, + IdxT k, + void* workspace, + size_t worksize, + bool isRowMajor = true, + DataT metric_arg = 2.0f) RAFT_EXPLICIT; + +template +size_t getWorkspaceSize(const DataT* x, const DataT* y, IdxT m, IdxT n, IdxT k) RAFT_EXPLICIT; + +template +size_t getWorkspaceSize(raft::device_matrix_view const& x, + raft::device_matrix_view const& y) RAFT_EXPLICIT; + +template +void distance(raft::resources const& handle, + const DataT* x, + const DataT* y, + OutT* dist, + IdxT m, + IdxT n, + IdxT k, + bool isRowMajor = true, + DataT metric_arg = 2.0f) RAFT_EXPLICIT; + +template +void pairwise_distance(raft::resources const& handle, + const Type* x, + const Type* y, + Type* dist, + IdxT m, + IdxT n, + IdxT k, + rmm::device_uvector& workspace, + cuvs::distance::DistanceType metric, + bool isRowMajor = true, + Type metric_arg = 2.0f) RAFT_EXPLICIT; + +template +void pairwise_distance(raft::resources const& handle, + const Type* x, + const Type* y, + Type* dist, + IdxT m, + IdxT n, + IdxT k, + cuvs::distance::DistanceType metric, + bool isRowMajor = true, + Type metric_arg = 2.0f) RAFT_EXPLICIT; + +template +void distance(raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + DataT metric_arg = 2.0f) RAFT_EXPLICIT; + +template +void pairwise_distance(raft::resources const& handle, + device_matrix_view const x, + device_matrix_view const y, + device_matrix_view dist, + cuvs::distance::DistanceType metric, + Type metric_arg = 2.0f) RAFT_EXPLICIT; + +}; // namespace distance +}; // namespace cuvs + +#endif // RAFT_EXPLICIT_INSTANTIATE_ONLY + +/* + * Hierarchy of instantiations: + * + * This file defines the extern template instantiations for the public API of + * cuvs::distance. To improve compile times, the extern template instantiation + * of the distance kernels is handled in + * distance/detail/pairwise_matrix/dispatch-ext.cuh. + * + * After adding an instance here, make sure to also add the instance to + * dispatch-ext.cuh and the corresponding .cu files. + */ + +#define instantiate_raft_distance_distance(DT, DataT, AccT, OutT, FinalLambda, IdxT) \ + extern template void cuvs::distance::distance( \ + raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + OutT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + void* workspace, \ + size_t worksize, \ + FinalLambda fin_op, \ + bool isRowMajor, \ + DataT metric_arg) + +// The following two instances are used in test/distance/gram.cu. Note the use +// of int64_t for the index type. +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + float, + float, + float, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); + +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_distance + +// Same, but without raft::identity_op +#define instantiate_raft_distance_distance(DT, DataT, AccT, OutT, IdxT) \ + extern template void cuvs::distance::distance( \ + raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + OutT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + void* workspace, \ + size_t worksize, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, int); + +#undef instantiate_raft_distance_distance + +// Same, but without workspace +#define instantiate_raft_distance_distance(DT, DataT, AccT, OutT, IdxT) \ + extern template void cuvs::distance::distance( \ + raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + OutT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, int); + +#undef instantiate_raft_distance_distance + +#define instantiate_raft_distance_getWorkspaceSize(DistT, DataT, AccT, OutT, IdxT) \ + extern template size_t cuvs::distance::getWorkspaceSize( \ + const DataT* x, const DataT* y, IdxT m, IdxT n, IdxT k) + +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CorrelationExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Linf, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Linf, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, int); + +#undef instantiate_raft_distance_getWorkspaceSize + +#define instantiate_raft_distance_getWorkspaceSize(DistT, DataT, AccT, OutT, IdxT, layout) \ + extern template size_t cuvs::distance::getWorkspaceSize( \ + raft::device_matrix_view const& x, \ + raft::device_matrix_view const& y) + +// We could consider not taking template parameters for this function. The +// number of instantiations seems a bit excessive.. +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, double, double, double, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, double, double, double, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, double, double, double, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, double, double, double, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int, raft::layout_f_contiguous); + +#undef instantiate_raft_distance_getWorkspaceSize + +#define instantiate_raft_distance_pairwise_distance(DataT, IdxT) \ + extern template void cuvs::distance::pairwise_distance(raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + DataT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + rmm::device_uvector& workspace, \ + cuvs::distance::DistanceType metric, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_pairwise_distance(float, int); +instantiate_raft_distance_pairwise_distance(double, int); + +#undef instantiate_raft_distance_pairwise_distance + +// Same, but without workspace +#define instantiate_raft_distance_pairwise_distance(DataT, IdxT) \ + extern template void cuvs::distance::pairwise_distance(raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + DataT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + cuvs::distance::DistanceType metric, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_pairwise_distance(float, int); +instantiate_raft_distance_pairwise_distance(double, int); + +#undef instantiate_raft_distance_pairwise_distance + +// Version with mdspan +#define instantiate_raft_distance_distance(DistT, DataT, AccT, OutT, layout, IdxT) \ + extern template void cuvs::distance::distance( \ + raft::resources const& handle, \ + raft::device_matrix_view const x, \ + raft::device_matrix_view const y, \ + raft::device_matrix_view dist, \ + DataT metric_arg) + +// Again, we might want to consider reigning in the number of instantiations... +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::LpUnexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::LpUnexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); + +#undef instantiate_raft_distance_distance + +#define instantiate_raft_distance_pairwise_distance(DataT, layout, IdxT) \ + extern template void cuvs::distance::pairwise_distance( \ + raft::resources const& handle, \ + raft::device_matrix_view const x, \ + raft::device_matrix_view const y, \ + raft::device_matrix_view dist, \ + cuvs::distance::DistanceType metric, \ + DataT metric_arg) + +instantiate_raft_distance_pairwise_distance(float, raft::layout_c_contiguous, int); +instantiate_raft_distance_pairwise_distance(float, raft::layout_f_contiguous, int); +instantiate_raft_distance_pairwise_distance(double, raft::layout_c_contiguous, int); +instantiate_raft_distance_pairwise_distance(double, raft::layout_f_contiguous, int); + +#undef instantiate_raft_distance_pairwise_distance diff --git a/cpp/src/distance/distance-inl.cuh b/cpp/src/distance/distance-inl.cuh new file mode 100644 index 0000000000..5b82f5438c --- /dev/null +++ b/cpp/src/distance/distance-inl.cuh @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "detail/distance.cuh" +#include +#include +#include +#include + +#include + +#include + +namespace cuvs { +namespace distance { + +/** + * @defgroup pairwise_distance pointer-based pairwise distance prims + * @{ + */ + +/** + * @brief Evaluate pairwise distances with the user epilogue lamba allowed + * @tparam DistanceType which distance to evaluate + * @tparam DataT input argument type + * @tparam AccT accumulation type + * @tparam OutT output type + * @tparam FinalLambda user-defined epilogue lamba + * @tparam IdxT Index type + * @param handle raft handle for managing expensive resources + * @param x first set of points + * @param y second set of points + * @param dist output distance matrix + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * @param workspace temporary workspace needed for computations + * @param worksize number of bytes of the workspace + * @param fin_op the final gemm epilogue lambda + * @param isRowMajor whether the matrices are row-major or col-major + * @param metric_arg metric argument (used for Minkowski distance) + * + * @note fin_op: This is a device lambda which is supposed to operate upon the + * input which is AccT and returns the output in OutT. It's signature is + * as follows:

OutT fin_op(AccT in, int g_idx);
. If one needs + * any other parameters, feel free to pass them via closure. + */ +template +void distance(raft::resources const& handle, + const DataT* x, + const DataT* y, + OutT* dist, + IdxT m, + IdxT n, + IdxT k, + void* workspace, + size_t worksize, + FinalLambda fin_op, + bool isRowMajor = true, + DataT metric_arg = 2.0f) +{ + detail::distance( + handle, x, y, dist, m, n, k, workspace, worksize, fin_op, isRowMajor, metric_arg); +} + +/** + * @brief Evaluate pairwise distances for the simple use case + * @tparam DistanceType which distance to evaluate + * @tparam DataT input argument type + * @tparam AccT accumulation type + * @tparam OutT output type + * @tparam IdxT Index type + * @param handle raft handle for managing expensive resources + * @param x first set of points + * @param y second set of points + * @param dist output distance matrix + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * @param workspace temporary workspace needed for computations + * @param worksize number of bytes of the workspace + * @param isRowMajor whether the matrices are row-major or col-major + * @param metric_arg metric argument (used for Minkowski distance) + */ +template +void distance(raft::resources const& handle, + const DataT* x, + const DataT* y, + OutT* dist, + IdxT m, + IdxT n, + IdxT k, + void* workspace, + size_t worksize, + bool isRowMajor = true, + DataT metric_arg = 2.0f) +{ + detail::distance( + handle, x, y, dist, m, n, k, workspace, worksize, isRowMajor, metric_arg); +} + +/** + * @brief Return the exact workspace size to compute the distance + * @tparam DistanceType which distance to evaluate + * @tparam DataT input argument type + * @tparam AccT accumulation type + * @tparam OutT output type + * @tparam IdxT Index type + * @param x first set of points + * @param y second set of points + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * + * @note If the specified DistT doesn't need the workspace at all, it + * returns 0. + */ +template +size_t getWorkspaceSize(const DataT* x, const DataT* y, IdxT m, IdxT n, IdxT k) +{ + return detail::getWorkspaceSize(x, y, m, n, k); +} + +/** + * @brief Return the exact workspace size to compute the distance + * @tparam DistanceType which distance to evaluate + * @tparam DataT input argument type + * @tparam AccT accumulation type + * @tparam OutT output type + * @tparam IdxT Index type + * @param x first set of points (size m*k) + * @param y second set of points (size n*k) + * @return number of bytes needed in workspace + * + * @note If the specified DistT doesn't need the workspace at all, it + * returns 0. + */ +template +size_t getWorkspaceSize(raft::device_matrix_view const& x, + raft::device_matrix_view const& y) +{ + RAFT_EXPECTS(x.extent(1) == y.extent(1), "Number of columns must be equal."); + + return getWorkspaceSize( + x.data_handle(), y.data_handle(), x.extent(0), y.extent(0), x.extent(1)); +} + +/** + * @brief Evaluate pairwise distances for the simple use case + * @tparam DistanceType which distance to evaluate + * @tparam DataT input argument type + * @tparam AccT accumulation type + * @tparam OutT output type + * @tparam IdxT Index type + * @param handle raft handle for managing expensive resources + * @param x first set of points + * @param y second set of points + * @param dist output distance matrix + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * @param isRowMajor whether the matrices are row-major or col-major + * @param metric_arg metric argument (used for Minkowski distance) + */ +template +void distance(raft::resources const& handle, + const DataT* x, + const DataT* y, + OutT* dist, + IdxT m, + IdxT n, + IdxT k, + bool isRowMajor = true, + DataT metric_arg = 2.0f) +{ + auto stream = raft::resource::get_cuda_stream(handle); + rmm::device_uvector workspace(0, stream); + auto worksize = getWorkspaceSize(x, y, m, n, k); + workspace.resize(worksize, stream); + detail::distance( + handle, x, y, dist, m, n, k, workspace.data(), worksize, isRowMajor, metric_arg); +} + +/** + * @brief Convenience wrapper around 'distance' prim to convert runtime metric + * into compile time for the purpose of dispatch + * @tparam Type input/accumulation/output data-type + * @tparam IdxT indexing type + * @param handle raft handle for managing expensive resources + * @param x first set of points + * @param y second set of points + * @param dist output distance matrix + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * @param workspace temporary workspace buffer which can get resized as per the + * needed workspace size + * @param metric distance metric + * @param isRowMajor whether the matrices are row-major or col-major + * @param metric_arg metric argument (used for Minkowski distance) + */ +template +void pairwise_distance(raft::resources const& handle, + const Type* x, + const Type* y, + Type* dist, + IdxT m, + IdxT n, + IdxT k, + rmm::device_uvector& workspace, + cuvs::distance::DistanceType metric, + bool isRowMajor = true, + Type metric_arg = 2.0f) +{ + cudaStream_t stream = raft::resource::get_cuda_stream(handle); + + auto dispatch = [&](auto distance_type) { + auto worksize = getWorkspaceSize(x, y, m, n, k); + workspace.resize(worksize, stream); + detail::distance( + handle, x, y, dist, m, n, k, workspace.data(), worksize, isRowMajor, metric_arg); + }; + + switch (metric) { + case DistanceType::Canberra: + dispatch(std::integral_constant{}); + break; + case DistanceType::CorrelationExpanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::CosineExpanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::HammingUnexpanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::HellingerExpanded: + dispatch(std::integral_constant{}); + break; + case cuvs::distance::DistanceType::InnerProduct: + dispatch(std::integral_constant{}); + break; + case DistanceType::JensenShannon: + dispatch(std::integral_constant{}); + break; + case DistanceType::KLDivergence: + dispatch(std::integral_constant{}); + break; + case DistanceType::L1: + dispatch(std::integral_constant{}); + break; + case DistanceType::L2Expanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::L2SqrtExpanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::L2SqrtUnexpanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::L2Unexpanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::Linf: + dispatch(std::integral_constant{}); + break; + case DistanceType::LpUnexpanded: + dispatch(std::integral_constant{}); + break; + case DistanceType::RusselRaoExpanded: + dispatch(std::integral_constant{}); + break; + default: THROW("Unknown or unsupported distance metric '%d'!", (int)metric); + }; +} + +/** + * @brief Convenience wrapper around 'distance' prim to convert runtime metric + * into compile time for the purpose of dispatch + * @tparam Type input/accumulation/output data-type + * @tparam IdxT indexing type + * @param handle raft handle for managing expensive resources + * @param x first set of points + * @param y second set of points + * @param dist output distance matrix + * @param m number of points in x + * @param n number of points in y + * @param k dimensionality + * @param metric distance metric + * @param isRowMajor whether the matrices are row-major or col-major + * @param metric_arg metric argument (used for Minkowski distance) + */ +template +void pairwise_distance(raft::resources const& handle, + const Type* x, + const Type* y, + Type* dist, + IdxT m, + IdxT n, + IdxT k, + cuvs::distance::DistanceType metric, + bool isRowMajor = true, + Type metric_arg = 2.0f) +{ + auto stream = raft::resource::get_cuda_stream(handle); + rmm::device_uvector workspace(0, stream); + pairwise_distance( + handle, x, y, dist, m, n, k, workspace, metric, isRowMajor, metric_arg); +} + +/** @} */ + +/** + * \defgroup distance_mdspan Pairwise distance functions + * @{ + */ + +/** + * @brief Evaluate pairwise distances for the simple use case. + * + * Note: Only contiguous row- or column-major layouts supported currently. + * + * Usage example: + * @code{.cpp} + * #include + * #include + * #include + * #include + * + * raft::raft::resources handle; + * int n_samples = 5000; + * int n_features = 50; + * + * auto input = raft::make_device_matrix(handle, n_samples, n_features); + * auto labels = raft::make_device_vector(handle, n_samples); + * auto output = raft::make_device_matrix(handle, n_samples, n_samples); + * + * raft::random::make_blobs(handle, input.view(), labels.view()); + * auto metric = cuvs::distance::DistanceType::L2SqrtExpanded; + * cuvs::distance::pairwise_distance(handle, input.view(), input.view(), output.view(), metric); + * @endcode + * + * @tparam DistanceType which distance to evaluate + * @tparam DataT input argument type + * @tparam AccT accumulation type + * @tparam OutT output type + * @tparam IdxT Index type + * @param handle raft handle for managing expensive resources + * @param x first set of points (size n*k) + * @param y second set of points (size m*k) + * @param dist output distance matrix (size n*m) + * @param metric_arg metric argument (used for Minkowski distance) + */ +template +void distance(raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + DataT metric_arg = 2.0f) +{ + RAFT_EXPECTS(x.extent(1) == y.extent(1), "Number of columns must be equal."); + RAFT_EXPECTS(dist.extent(0) == x.extent(0), + "Number of rows in output must be equal to " + "number of rows in X"); + RAFT_EXPECTS(dist.extent(1) == y.extent(0), + "Number of columns in output must be equal to " + "number of rows in Y"); + + RAFT_EXPECTS(x.is_exhaustive(), "Input x must be contiguous."); + RAFT_EXPECTS(y.is_exhaustive(), "Input y must be contiguous."); + + constexpr auto is_rowmajor = std::is_same_v; + + distance(handle, + x.data_handle(), + y.data_handle(), + dist.data_handle(), + x.extent(0), + y.extent(0), + x.extent(1), + is_rowmajor, + metric_arg); +} + +/** + * @brief Convenience wrapper around 'distance' prim to convert runtime metric + * into compile time for the purpose of dispatch + * @tparam Type input/accumulation/output data-type + * @tparam IdxT indexing type + * @param handle raft handle for managing expensive resources + * @param x first matrix of points (size mxk) + * @param y second matrix of points (size nxk) + * @param dist output distance matrix (size mxn) + * @param metric distance metric + * @param metric_arg metric argument (used for Minkowski distance) + */ +template +void pairwise_distance(raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + Type metric_arg = 2.0f) +{ + RAFT_EXPECTS(x.extent(1) == y.extent(1), "Number of columns must be equal."); + RAFT_EXPECTS(dist.extent(0) == x.extent(0), + "Number of rows in output must be equal to " + "number of rows in X"); + RAFT_EXPECTS(dist.extent(1) == y.extent(0), + "Number of columns in output must be equal to " + "number of rows in Y"); + + RAFT_EXPECTS(x.is_exhaustive(), "Input x must be contiguous."); + RAFT_EXPECTS(y.is_exhaustive(), "Input y must be contiguous."); + RAFT_EXPECTS(dist.is_exhaustive(), "Output must be contiguous."); + + constexpr auto rowmajor = std::is_same_v; + + auto stream = raft::resource::get_cuda_stream(handle); + rmm::device_uvector workspace(0, stream); + + pairwise_distance(handle, + x.data_handle(), + y.data_handle(), + dist.data_handle(), + x.extent(0), + y.extent(0), + x.extent(1), + metric, + rowmajor, + metric_arg); +} + +/** @} */ + +}; // namespace distance +}; // namespace cuvs diff --git a/cpp/src/distance/distance.cu b/cpp/src/distance/distance.cu new file mode 100644 index 0000000000..02c071d13c --- /dev/null +++ b/cpp/src/distance/distance.cu @@ -0,0 +1,934 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "detail/kernels/rbf_fin_op.cuh" // rbf_fin_op +#include "distance-inl.cuh" + +/* + * Hierarchy of instantiations: + * + * This file defines the template instantiations for the public API of + * cuvs::distance. To improve compile times, the compilation of the distance + * kernels is handled in distance/detail/pairwise_matrix/dispatch_*.cu. + * + */ + +#define instantiate_raft_distance_distance(DT, DataT, AccT, OutT, FinalLambda, IdxT) \ + template void cuvs::distance::distance( \ + raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + OutT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + void* workspace, \ + size_t worksize, \ + FinalLambda fin_op, \ + bool isRowMajor, \ + DataT metric_arg) + +// The following two instances are used in test/distance/gram.cu. Note the use +// of int64_t for the index type. +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + float, + float, + float, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + cuvs::distance::kernels::detail::rbf_fin_op, + int64_t); + +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + raft::identity_op, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, raft::identity_op, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, raft::identity_op, int); + +#undef instantiate_raft_distance_distance + +// Same, but without raft::identity_op +#define instantiate_raft_distance_distance(DT, DataT, AccT, OutT, IdxT) \ + template void cuvs::distance::distance( \ + raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + OutT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + void* workspace, \ + size_t worksize, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, int); + +#undef instantiate_raft_distance_distance + +// Same, but without workspace +#define instantiate_raft_distance_distance(DT, DataT, AccT, OutT, IdxT) \ + template void cuvs::distance::distance( \ + raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + OutT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CorrelationExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L1, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, float, float, float, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::Linf, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, int); + +#undef instantiate_raft_distance_distance + +#define instantiate_raft_distance_getWorkspaceSize(DistT, DataT, AccT, OutT, IdxT) \ + template size_t cuvs::distance::getWorkspaceSize( \ + const DataT* x, const DataT* y, IdxT m, IdxT n, IdxT k) + +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CorrelationExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CorrelationExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CosineExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::CosineExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HammingUnexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HammingUnexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HellingerExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::HellingerExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtExpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtUnexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2SqrtUnexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Linf, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Linf, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::LpUnexpanded, double, double, double, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::RusselRaoExpanded, float, float, float, int); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::RusselRaoExpanded, double, double, double, int); + +#undef instantiate_raft_distance_getWorkspaceSize + +#define instantiate_raft_distance_getWorkspaceSize(DistT, DataT, AccT, OutT, IdxT, layout) \ + template size_t cuvs::distance::getWorkspaceSize( \ + raft::device_matrix_view const& x, \ + raft::device_matrix_view const& y) + +// We could consider not taking template parameters for this function. The +// number of instantiations seems a bit excessive.. +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, double, double, double, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::Canberra, double, double, double, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::InnerProduct, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::JensenShannon, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::KLDivergence, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, double, double, double, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L1, double, double, double, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, float, float, float, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Expanded, double, double, double, int, raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + int, + raft::layout_f_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int, raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + int, + raft::layout_c_contiguous); +instantiate_raft_distance_getWorkspaceSize( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, int, raft::layout_f_contiguous); + +#undef instantiate_raft_distance_getWorkspaceSize + +#define instantiate_raft_distance_pairwise_distance(DataT, IdxT) \ + template void cuvs::distance::pairwise_distance(raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + DataT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + rmm::device_uvector& workspace, \ + cuvs::distance::DistanceType metric, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_pairwise_distance(float, int); +instantiate_raft_distance_pairwise_distance(double, int); + +#undef instantiate_raft_distance_pairwise_distance + +// Same, but without workspace +#define instantiate_raft_distance_pairwise_distance(DataT, IdxT) \ + template void cuvs::distance::pairwise_distance(raft::resources const& handle, \ + const DataT* x, \ + const DataT* y, \ + DataT* dist, \ + IdxT m, \ + IdxT n, \ + IdxT k, \ + cuvs::distance::DistanceType metric, \ + bool isRowMajor, \ + DataT metric_arg) + +instantiate_raft_distance_pairwise_distance(float, int); +instantiate_raft_distance_pairwise_distance(double, int); + +#undef instantiate_raft_distance_pairwise_distance + +// Version with mdspan +#define instantiate_raft_distance_distance(DistT, DataT, AccT, OutT, layout, IdxT) \ + template void cuvs::distance::distance( \ + raft::resources const& handle, \ + raft::device_matrix_view const x, \ + raft::device_matrix_view const y, \ + raft::device_matrix_view dist, \ + DataT metric_arg) + +// Again, we might want to consider reigning in the number of instantiations... +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Canberra, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CorrelationExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::CosineExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HammingUnexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::HellingerExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::InnerProduct, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::InnerProduct, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::JensenShannon, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::JensenShannon, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::KLDivergence, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::KLDivergence, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L1, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Expanded, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2SqrtUnexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::L2Unexpanded, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::L2Unexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, double, double, double, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::Linf, double, double, double, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, raft::layout_c_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::LpUnexpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance( + cuvs::distance::DistanceType::LpUnexpanded, float, float, float, raft::layout_f_contiguous, int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::LpUnexpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + float, + float, + float, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + double, + double, + double, + raft::layout_c_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + float, + float, + float, + raft::layout_f_contiguous, + int); +instantiate_raft_distance_distance(cuvs::distance::DistanceType::RusselRaoExpanded, + double, + double, + double, + raft::layout_f_contiguous, + int); + +#undef instantiate_raft_distance_distance + +#define instantiate_raft_distance_pairwise_distance(DataT, layout, IdxT) \ + template void cuvs::distance::pairwise_distance( \ + raft::resources const& handle, \ + raft::device_matrix_view const x, \ + raft::device_matrix_view const y, \ + raft::device_matrix_view dist, \ + cuvs::distance::DistanceType metric, \ + DataT metric_arg) + +instantiate_raft_distance_pairwise_distance(float, raft::layout_c_contiguous, int); +instantiate_raft_distance_pairwise_distance(float, raft::layout_f_contiguous, int); +instantiate_raft_distance_pairwise_distance(double, raft::layout_c_contiguous, int); +instantiate_raft_distance_pairwise_distance(double, raft::layout_f_contiguous, int); + +#undef instantiate_raft_distance_pairwise_distance diff --git a/cpp/src/distance/distance.cuh b/cpp/src/distance/distance.cuh new file mode 100644 index 0000000000..de70cd4691 --- /dev/null +++ b/cpp/src/distance/distance.cuh @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#ifndef RAFT_EXPLICIT_INSTANTIATE_ONLY +#include "distance-inl.cuh" +#endif + +#ifdef RAFT_COMPILED +#include "distance-ext.cuh" +#endif diff --git a/cpp/src/distance/pairwise_distance.cu b/cpp/src/distance/pairwise_distance.cu new file mode 100644 index 0000000000..bf4b21669b --- /dev/null +++ b/cpp/src/distance/pairwise_distance.cu @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "distance.cuh" +#include +#include +#include +#include + +namespace cuvs::distance { + +/** + * @defgroup pairwise_distance_runtime Pairwise Distances Runtime API + * @{ + */ +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + float metric_arg) +{ + auto x_v = raft::make_device_matrix_view( + x.data_handle(), x.extent(0), x.extent(1)); + auto y_v = raft::make_device_matrix_view( + y.data_handle(), y.extent(0), y.extent(1)); + auto d_v = raft::make_device_matrix_view( + dist.data_handle(), dist.extent(0), dist.extent(1)); + pairwise_distance( + handle, x_v, y_v, d_v, metric, metric_arg); +} + +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + double metric_arg) +{ + auto x_v = raft::make_device_matrix_view( + x.data_handle(), x.extent(0), x.extent(1)); + auto y_v = raft::make_device_matrix_view( + y.data_handle(), y.extent(0), y.extent(1)); + auto d_v = raft::make_device_matrix_view( + dist.data_handle(), dist.extent(0), dist.extent(1)); + pairwise_distance( + handle, x_v, y_v, d_v, metric, metric_arg); +} + +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + float metric_arg) +{ + auto x_v = raft::make_device_matrix_view( + x.data_handle(), x.extent(0), x.extent(1)); + auto y_v = raft::make_device_matrix_view( + y.data_handle(), y.extent(0), y.extent(1)); + auto d_v = raft::make_device_matrix_view( + dist.data_handle(), dist.extent(0), dist.extent(1)); + pairwise_distance( + handle, x_v, y_v, d_v, metric, metric_arg); +} + +void pairwise_distance( + raft::resources const& handle, + raft::device_matrix_view const x, + raft::device_matrix_view const y, + raft::device_matrix_view dist, + cuvs::distance::DistanceType metric, + double metric_arg) +{ + auto x_v = raft::make_device_matrix_view( + x.data_handle(), x.extent(0), x.extent(1)); + auto y_v = raft::make_device_matrix_view( + y.data_handle(), y.extent(0), y.extent(1)); + auto d_v = raft::make_device_matrix_view( + dist.data_handle(), dist.extent(0), dist.extent(1)); + pairwise_distance( + handle, x_v, y_v, d_v, metric, metric_arg); +} + +/** @} */ // end group pairwise_distance_runtime + +} // namespace cuvs::distance diff --git a/cpp/src/neighbors/brute_force_c.cpp b/cpp/src/neighbors/brute_force_c.cpp index 88349e089e..e988ac2f0c 100644 --- a/cpp/src/neighbors/brute_force_c.cpp +++ b/cpp/src/neighbors/brute_force_c.cpp @@ -33,7 +33,7 @@ namespace { template void* _build(cuvsResources_t res, DLManagedTensor* dataset_tensor, - enum DistanceType metric, + cuvsDistanceType metric, T metric_arg) { auto res_ptr = reinterpret_cast(res); @@ -97,7 +97,7 @@ extern "C" cuvsError_t cuvsBruteForceIndexDestroy(cuvsBruteForceIndex_t index_c_ extern "C" cuvsError_t cuvsBruteForceBuild(cuvsResources_t res, DLManagedTensor* dataset_tensor, - enum DistanceType metric, + cuvsDistanceType metric, float metric_arg, cuvsBruteForceIndex_t index) { diff --git a/cpp/src/neighbors/cagra_build_float.cpp b/cpp/src/neighbors/cagra_build_float.cpp index f66ae5cfb9..5ff1da7f21 100644 --- a/cpp/src/neighbors/cagra_build_float.cpp +++ b/cpp/src/neighbors/cagra_build_float.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024, NVIDIA CORPORATION. + * Copyright (c) 2024, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/cpp/test/CMakeLists.txt b/cpp/test/CMakeLists.txt index 841058f60f..131737039a 100644 --- a/cpp/test/CMakeLists.txt +++ b/cpp/test/CMakeLists.txt @@ -128,35 +128,47 @@ if(BUILD_TESTS) PERCENT 100 ) + + ConfigureTest( + NAME + DISTANCE_TEST + PATH + test/distance/dist_canberra.cu + test/distance/dist_correlation.cu + test/distance/dist_cos.cu + test/distance/dist_hamming.cu + test/distance/dist_hellinger.cu + test/distance/dist_inner_product.cu + test/distance/dist_jensen_shannon.cu + test/distance/dist_kl_divergence.cu + test/distance/dist_l1.cu + test/distance/dist_l2_exp.cu + test/distance/dist_l2_sqrt_exp.cu + test/distance/dist_l_inf.cu + test/distance/dist_lp_unexp.cu + test/distance/dist_russell_rao.cu + GPUS + 1 + PERCENT + 100 + ) endif() if(BUILD_C_TESTS) ConfigureTest(NAME INTEROP_TEST PATH test/core/interop.cu C_LIB) ConfigureTest( - NAME - BRUTEFORCE_C_TEST - PATH - test/neighbors/run_brute_force_c.c - test/neighbors/brute_force_c.cu + NAME BRUTEFORCE_C_TEST PATH test/neighbors/run_brute_force_c.c test/neighbors/brute_force_c.cu C_LIB ) ConfigureTest( - NAME - IVF_FLAT_C_TEST - PATH - test/neighbors/run_ivf_flat_c.c - test/neighbors/ann_ivf_flat_c.cu + NAME IVF_FLAT_C_TEST PATH test/neighbors/run_ivf_flat_c.c test/neighbors/ann_ivf_flat_c.cu C_LIB ) ConfigureTest( - NAME - IVF_PQ_C_TEST - PATH - test/neighbors/run_ivf_pq_c.c - test/neighbors/ann_ivf_pq_c.cu C_LIB + NAME IVF_PQ_C_TEST PATH test/neighbors/run_ivf_pq_c.c test/neighbors/ann_ivf_pq_c.cu C_LIB ) ConfigureTest(NAME CAGRA_C_TEST PATH test/neighbors/ann_cagra_c.cu C_LIB) diff --git a/cpp/test/distance/dist_canberra.cu b/cpp/test/distance/dist_canberra.cu new file mode 100644 index 0000000000..2bf5906013 --- /dev/null +++ b/cpp/test/distance/dist_canberra.cu @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceCanberra : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceCanberra DistanceCanberraF; +TEST_P(DistanceCanberraF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceCanberraF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceCanberra DistanceCanberraD; +TEST_P(DistanceCanberraD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceCanberraD, ::testing::ValuesIn(inputsd)); + +class BigMatrixCanberra : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixCanberra, Result) {} + +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_correlation.cu b/cpp/test/distance/dist_correlation.cu new file mode 100644 index 0000000000..9e061bebc3 --- /dev/null +++ b/cpp/test/distance/dist_correlation.cu @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceCorrelation + : public DistanceTest {}; + +template +class DistanceCorrelationXequalY + : public DistanceTestSameBuffer {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceCorrelation DistanceCorrelationF; +TEST_P(DistanceCorrelationF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceCorrelationF, ::testing::ValuesIn(inputsf)); + +typedef DistanceCorrelationXequalY DistanceCorrelationXequalYF; +TEST_P(DistanceCorrelationXequalYF, Result) +{ + int m = params.m; + ASSERT_TRUE(cuvs::devArrMatch(dist_ref[0].data(), + dist[0].data(), + m, + m, + cuvs::CompareApprox(params.tolerance), + stream)); + ASSERT_TRUE(cuvs::devArrMatch(dist_ref[1].data(), + dist[1].data(), + m / 2, + m, + cuvs::CompareApprox(params.tolerance), + stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceCorrelationXequalYF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceCorrelation DistanceCorrelationD; +TEST_P(DistanceCorrelationD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceCorrelationD, ::testing::ValuesIn(inputsd)); + +class BigMatrixCorrelation + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixCorrelation, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_cos.cu b/cpp/test/distance/dist_cos.cu new file mode 100644 index 0000000000..e134f045fc --- /dev/null +++ b/cpp/test/distance/dist_cos.cu @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceExpCos : public DistanceTest { +}; + +template +class DistanceExpCosXequalY + : public DistanceTestSameBuffer {}; + +const std::vector> inputsf = { + {0.001f, 128, (65536 + 128) * 128, 8, true, 1234ULL}, + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, (65536 + 128) * 128, 128, 8, false, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; + +const std::vector> inputsXeqYf = { + {0.01f, 1024, 1024, 32, true, 1234ULL}, + {0.01f, 1024, 32, 1024, true, 1234ULL}, + {0.01f, 32, 1024, 1024, true, 1234ULL}, + {0.03f, 1024, 1024, 1024, true, 1234ULL}, + {0.01f, 1024, 1024, 32, false, 1234ULL}, + {0.01f, 1024, 32, 1024, false, 1234ULL}, + {0.01f, 32, 1024, 1024, false, 1234ULL}, + {0.03f, 1024, 1024, 1024, false, 1234ULL}, +}; + +typedef DistanceExpCos DistanceExpCosF; +TEST_P(DistanceExpCosF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceExpCosF, ::testing::ValuesIn(inputsf)); + +typedef DistanceExpCosXequalY DistanceExpCosXequalYF; +TEST_P(DistanceExpCosXequalYF, Result) +{ + int m = params.m; + int n = params.m; + ASSERT_TRUE(cuvs::devArrMatch(dist_ref[0].data(), + dist[0].data(), + m, + n, + cuvs::CompareApprox(params.tolerance), + stream)); + n = params.isRowMajor ? m : m / 2; + m = params.isRowMajor ? m / 2 : m; + + ASSERT_TRUE(cuvs::devArrMatch(dist_ref[1].data(), + dist[1].data(), + m, + n, + cuvs::CompareApprox(params.tolerance), + stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceExpCosXequalYF, ::testing::ValuesIn(inputsXeqYf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceExpCos DistanceExpCosD; +TEST_P(DistanceExpCosD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceExpCosD, ::testing::ValuesIn(inputsd)); + +class BigMatrixCos : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixCos, Result) {} + +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_hamming.cu b/cpp/test/distance/dist_hamming.cu new file mode 100644 index 0000000000..0cf753ecae --- /dev/null +++ b/cpp/test/distance/dist_hamming.cu @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceHamming + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceHamming DistanceHammingF; +TEST_P(DistanceHammingF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceHammingF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceHamming DistanceHammingD; +TEST_P(DistanceHammingD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceHammingD, ::testing::ValuesIn(inputsd)); + +class BigMatrixHamming + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixHamming, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_hellinger.cu b/cpp/test/distance/dist_hellinger.cu new file mode 100644 index 0000000000..3998a60ab1 --- /dev/null +++ b/cpp/test/distance/dist_hellinger.cu @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceHellingerExp + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceHellingerExp DistanceHellingerExpF; +TEST_P(DistanceHellingerExpF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceHellingerExpF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceHellingerExp DistanceHellingerExpD; +TEST_P(DistanceHellingerExpD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceHellingerExpD, ::testing::ValuesIn(inputsd)); + +class BigMatrixHellingerExp + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixHellingerExp, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_inner_product.cu b/cpp/test/distance/dist_inner_product.cu new file mode 100644 index 0000000000..1d6709d52b --- /dev/null +++ b/cpp/test/distance/dist_inner_product.cu @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceInnerProduct + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 10, 5, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceInnerProduct DistanceInnerProductF; +TEST_P(DistanceInnerProductF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceInnerProductF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceInnerProduct DistanceInnerProductD; +TEST_P(DistanceInnerProductD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceInnerProductD, ::testing::ValuesIn(inputsd)); + +class BigMatrixInnerProduct + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixInnerProduct, Result) {} + +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_jensen_shannon.cu b/cpp/test/distance/dist_jensen_shannon.cu new file mode 100644 index 0000000000..43b7b361d1 --- /dev/null +++ b/cpp/test/distance/dist_jensen_shannon.cu @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceJensenShannon + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceJensenShannon DistanceJensenShannonF; +TEST_P(DistanceJensenShannonF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceJensenShannonF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceJensenShannon DistanceJensenShannonD; +TEST_P(DistanceJensenShannonD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceJensenShannonD, ::testing::ValuesIn(inputsd)); + +class BigMatrixJensenShannon + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixJensenShannon, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_kl_divergence.cu b/cpp/test/distance/dist_kl_divergence.cu new file mode 100644 index 0000000000..5e56928411 --- /dev/null +++ b/cpp/test/distance/dist_kl_divergence.cu @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceKLDivergence + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceKLDivergence DistanceKLDivergenceF; +TEST_P(DistanceKLDivergenceF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceKLDivergenceF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceKLDivergence DistanceKLDivergenceD; +TEST_P(DistanceKLDivergenceD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceKLDivergenceD, ::testing::ValuesIn(inputsd)); + +class BigMatrixKLDivergence + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixKLDivergence, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_l1.cu b/cpp/test/distance/dist_l1.cu new file mode 100644 index 0000000000..a3ecd21fe0 --- /dev/null +++ b/cpp/test/distance/dist_l1.cu @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceUnexpL1 : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceUnexpL1 DistanceUnexpL1F; +TEST_P(DistanceUnexpL1F, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceUnexpL1F, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceUnexpL1 DistanceUnexpL1D; +TEST_P(DistanceUnexpL1D, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceUnexpL1D, ::testing::ValuesIn(inputsd)); + +class BigMatrixUnexpL1 : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixUnexpL1, Result) {} + +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_l2_exp.cu b/cpp/test/distance/dist_l2_exp.cu new file mode 100644 index 0000000000..f3d038cbc9 --- /dev/null +++ b/cpp/test/distance/dist_l2_exp.cu @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceEucExpTest : public DistanceTest { +}; + +template +class DistanceEucExpTestXequalY + : public DistanceTestSameBuffer {}; + +const std::vector> inputsf = { + {0.001f, 128, (65536 + 128) * 128, 8, true, 1234ULL}, + {0.001f, 2048, 4096, 128, true, 1234ULL}, + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.003f, 1021, 1021, 1021, true, 1234ULL}, + {0.001f, (65536 + 128) * 128, 128, 8, false, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, + {0.003f, 1021, 1021, 1021, false, 1234ULL}, +}; + +const std::vector> inputsXeqYf = { + {0.01f, 2048, 4096, 128, true, 1234ULL}, + {0.01f, 1024, 1024, 32, true, 1234ULL}, + {0.01f, 1024, 32, 1024, true, 1234ULL}, + {0.01f, 32, 1024, 1024, true, 1234ULL}, + {0.03f, 1024, 1024, 1024, true, 1234ULL}, + {0.03f, 1021, 1021, 1021, true, 1234ULL}, + {0.01f, 1024, 1024, 32, false, 1234ULL}, + {0.01f, 1024, 32, 1024, false, 1234ULL}, + {0.01f, 32, 1024, 1024, false, 1234ULL}, + {0.03f, 1024, 1024, 1024, false, 1234ULL}, + {0.03f, 1021, 1021, 1021, false, 1234ULL}, +}; + +typedef DistanceEucExpTest DistanceEucExpTestF; +TEST_P(DistanceEucExpTestF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceEucExpTestF, ::testing::ValuesIn(inputsf)); + +typedef DistanceEucExpTestXequalY DistanceEucExpTestXequalYF; +TEST_P(DistanceEucExpTestXequalYF, Result) +{ + int m = params.m; + ASSERT_TRUE(cuvs::devArrMatch(dist_ref[0].data(), + dist[0].data(), + m, + m, + cuvs::CompareApprox(params.tolerance), + stream)); + ASSERT_TRUE(cuvs::devArrMatch(dist_ref[1].data(), + dist[1].data(), + m / 2, + m, + cuvs::CompareApprox(params.tolerance), + stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, + DistanceEucExpTestXequalYF, + ::testing::ValuesIn(inputsXeqYf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceEucExpTest DistanceEucExpTestD; +TEST_P(DistanceEucExpTestD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceEucExpTestD, ::testing::ValuesIn(inputsd)); + +class BigMatrixEucExp : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixEucExp, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_l2_sqrt_exp.cu b/cpp/test/distance/dist_l2_sqrt_exp.cu new file mode 100644 index 0000000000..b24384be8a --- /dev/null +++ b/cpp/test/distance/dist_l2_sqrt_exp.cu @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceEucSqrtExpTest + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 2048, 4096, 128, true, 1234ULL}, + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.003f, 1021, 1021, 1021, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, + {0.003f, 1021, 1021, 1021, false, 1234ULL}, +}; +typedef DistanceEucSqrtExpTest DistanceEucSqrtExpTestF; +TEST_P(DistanceEucSqrtExpTestF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceEucSqrtExpTestF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceEucSqrtExpTest DistanceEucSqrtExpTestD; +TEST_P(DistanceEucSqrtExpTestD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceEucSqrtExpTestD, ::testing::ValuesIn(inputsd)); + +class BigMatrixEucSqrtExp + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixEucSqrtExp, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_l2_unexp.cu b/cpp/test/distance/dist_l2_unexp.cu new file mode 100644 index 0000000000..c057434fae --- /dev/null +++ b/cpp/test/distance/dist_l2_unexp.cu @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceEucUnexpTest + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceEucUnexpTest DistanceEucUnexpTestF; +TEST_P(DistanceEucUnexpTestF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceEucUnexpTestF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceEucUnexpTest DistanceEucUnexpTestD; +TEST_P(DistanceEucUnexpTestD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceEucUnexpTestD, ::testing::ValuesIn(inputsd)); + +class BigMatrixEucUnexp : public BigMatrixDistanceTest { +}; +TEST_F(BigMatrixEucUnexp, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_l_inf.cu b/cpp/test/distance/dist_l_inf.cu new file mode 100644 index 0000000000..b9ced68f34 --- /dev/null +++ b/cpp/test/distance/dist_l_inf.cu @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceLinf : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceLinf DistanceLinfF; +TEST_P(DistanceLinfF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceLinfF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceLinf DistanceLinfD; +TEST_P(DistanceLinfD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceLinfD, ::testing::ValuesIn(inputsd)); + +class BigMatrixLinf : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixLinf, Result) {} + +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_lp_unexp.cu b/cpp/test/distance/dist_lp_unexp.cu new file mode 100644 index 0000000000..26620b44b3 --- /dev/null +++ b/cpp/test/distance/dist_lp_unexp.cu @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceLpUnexp : public DistanceTest { +}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL, 4.0f}, + {0.001f, 1024, 32, 1024, true, 1234ULL, 3.0f}, + {0.001f, 32, 1024, 1024, true, 1234ULL, 4.0f}, + {0.003f, 1024, 1024, 1024, true, 1234ULL, 3.0f}, + {0.001f, 1024, 1024, 32, false, 1234ULL, 4.0f}, + {0.001f, 1024, 32, 1024, false, 1234ULL, 3.0f}, + {0.001f, 32, 1024, 1024, false, 1234ULL, 4.0f}, + {0.003f, 1024, 1024, 1024, false, 1234ULL, 3.0f}, +}; +typedef DistanceLpUnexp DistanceLpUnexpF; +TEST_P(DistanceLpUnexpF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceLpUnexpF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL, 4.0}, + {0.001, 1024, 32, 1024, true, 1234ULL, 3.0}, + {0.001, 32, 1024, 1024, true, 1234ULL, 4.0}, + {0.003, 1024, 1024, 1024, true, 1234ULL, 3.0}, + {0.001, 1024, 1024, 32, false, 1234ULL, 4.0}, + {0.001, 1024, 32, 1024, false, 1234ULL, 3.0}, + {0.001, 32, 1024, 1024, false, 1234ULL, 4.0}, + {0.003, 1024, 1024, 1024, false, 1234ULL, 3.0}, +}; +typedef DistanceLpUnexp DistanceLpUnexpD; +TEST_P(DistanceLpUnexpD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceLpUnexpD, ::testing::ValuesIn(inputsd)); + +class BigMatrixLpUnexp : public BigMatrixDistanceTest { +}; +TEST_F(BigMatrixLpUnexp, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/dist_russell_rao.cu b/cpp/test/distance/dist_russell_rao.cu new file mode 100644 index 0000000000..46da7f9cd0 --- /dev/null +++ b/cpp/test/distance/dist_russell_rao.cu @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "distance_base.cuh" + +namespace cuvs { +namespace distance { + +template +class DistanceRussellRao + : public DistanceTest {}; + +const std::vector> inputsf = { + {0.001f, 1024, 1024, 32, true, 1234ULL}, + {0.001f, 1024, 32, 1024, true, 1234ULL}, + {0.001f, 32, 1024, 1024, true, 1234ULL}, + {0.003f, 1024, 1024, 1024, true, 1234ULL}, + {0.001f, 1024, 1024, 32, false, 1234ULL}, + {0.001f, 1024, 32, 1024, false, 1234ULL}, + {0.001f, 32, 1024, 1024, false, 1234ULL}, + {0.003f, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceRussellRao DistanceRussellRaoF; +TEST_P(DistanceRussellRaoF, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceRussellRaoF, ::testing::ValuesIn(inputsf)); + +const std::vector> inputsd = { + {0.001, 1024, 1024, 32, true, 1234ULL}, + {0.001, 1024, 32, 1024, true, 1234ULL}, + {0.001, 32, 1024, 1024, true, 1234ULL}, + {0.003, 1024, 1024, 1024, true, 1234ULL}, + {0.001, 1024, 1024, 32, false, 1234ULL}, + {0.001, 1024, 32, 1024, false, 1234ULL}, + {0.001, 32, 1024, 1024, false, 1234ULL}, + {0.003, 1024, 1024, 1024, false, 1234ULL}, +}; +typedef DistanceRussellRao DistanceRussellRaoD; +TEST_P(DistanceRussellRaoD, Result) +{ + int m = params.isRowMajor ? params.m : params.n; + int n = params.isRowMajor ? params.n : params.m; + ASSERT_TRUE(cuvs::devArrMatch( + dist_ref.data(), dist.data(), m, n, cuvs::CompareApprox(params.tolerance), stream)); +} +INSTANTIATE_TEST_CASE_P(DistanceTests, DistanceRussellRaoD, ::testing::ValuesIn(inputsd)); + +class BigMatrixRussellRao + : public BigMatrixDistanceTest {}; +TEST_F(BigMatrixRussellRao, Result) {} +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/distance/distance_base.cuh b/cpp/test/distance/distance_base.cuh new file mode 100644 index 0000000000..4a8191ae64 --- /dev/null +++ b/cpp/test/distance/distance_base.cuh @@ -0,0 +1,710 @@ +/* + * Copyright (c) 2018-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" + +#include // cuvs::distance::DistanceType +#include +#include // raft::common::nvtx::range +#include //raft::make_device_matrix_view +#include // raft::sqrt +#include +#include // raft::resources +#include + +#include // rmm::device_uvector + +#include + +namespace cuvs { +namespace distance { + +template +RAFT_KERNEL naiveDistanceKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + cuvs::distance::DistanceType type, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) return; + DataType acc = DataType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto diff = x[xidx] - y[yidx]; + acc += diff * diff; + } + if (type == cuvs::distance::DistanceType::L2SqrtExpanded || + type == cuvs::distance::DistanceType::L2SqrtUnexpanded) + acc = raft::sqrt(acc); + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +RAFT_KERNEL naiveL1_Linf_CanberraDistanceKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + cuvs::distance::DistanceType type, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) { return; } + + DataType acc = DataType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + auto diff = (a > b) ? (a - b) : (b - a); + if (type == cuvs::distance::DistanceType::Linf) { + acc = raft::max(acc, diff); + } else if (type == cuvs::distance::DistanceType::Canberra) { + const auto add = raft::abs(a) + raft::abs(b); + // deal with potential for 0 in denominator by + // forcing 1/0 instead + acc += ((add != 0) * diff / (add + (add == 0))); + } else { + acc += diff; + } + } + + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +RAFT_KERNEL naiveCosineDistanceKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) { return; } + + DataType acc_a = DataType(0); + DataType acc_b = DataType(0); + DataType acc_ab = DataType(0); + + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + acc_a += a * a; + acc_b += b * b; + acc_ab += a * b; + } + + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + + // Use 1.0 - (cosine similarity) to calc the distance + dist[outidx] = (DataType)1.0 - acc_ab / (raft::sqrt(acc_a) * raft::sqrt(acc_b)); +} + +template +RAFT_KERNEL naiveInnerProductKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) { return; } + + DataType acc_ab = DataType(0); + + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + acc_ab += a * b; + } + + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc_ab; +} + +template +RAFT_KERNEL naiveHellingerDistanceKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) { return; } + + DataType acc_ab = DataType(0); + + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + acc_ab += raft::sqrt(a) * raft::sqrt(b); + } + + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + + // Adjust to replace NaN in sqrt with 0 if input to sqrt is negative + acc_ab = 1 - acc_ab; + auto rectifier = (!signbit(acc_ab)); + dist[outidx] = raft::sqrt(rectifier * acc_ab); +} + +template +RAFT_KERNEL naiveLpUnexpDistanceKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor, + DataType p) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) return; + DataType acc = DataType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + auto diff = raft::abs(a - b); + acc += raft::pow(diff, p); + } + auto one_over_p = 1 / p; + acc = raft::pow(acc, one_over_p); + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +RAFT_KERNEL naiveHammingDistanceKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) return; + DataType acc = DataType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + acc += (a != b); + } + acc = acc / k; + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +RAFT_KERNEL naiveJensenShannonDistanceKernel(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) return; + DataType acc = DataType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + + DataType m = 0.5f * (a + b); + bool a_zero = a == 0; + bool b_zero = b == 0; + + DataType p = (!a_zero * m) / (a_zero + a); + DataType q = (!b_zero * m) / (b_zero + b); + + bool p_zero = p == 0; + bool q_zero = q == 0; + + acc += (-a * (!p_zero * log(p + p_zero))) + (-b * (!q_zero * log(q + q_zero))); + } + acc = raft::sqrt(0.5f * acc); + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +RAFT_KERNEL naiveRussellRaoDistanceKernel(OutType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) return; + OutType acc = OutType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + acc += (a * b); + } + acc = (k - acc) / k; + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +RAFT_KERNEL naiveKLDivergenceDistanceKernel(OutType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) return; + OutType acc = OutType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + bool b_zero = (b == 0); + bool a_zero = (a == 0); + acc += a * (log(a + a_zero) - log(b + b_zero)); + } + acc = 0.5f * acc; + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +RAFT_KERNEL naiveCorrelationDistanceKernel(OutType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + bool isRowMajor) +{ + std::int64_t midx = threadIdx.x + blockIdx.x * blockDim.x; + std::int64_t nidx = threadIdx.y + blockIdx.y * blockDim.y; + if (midx >= m || nidx >= n) return; + OutType acc = OutType(0); + auto a_norm = DataType(0); + auto b_norm = DataType(0); + auto a_sq_norm = DataType(0); + auto b_sq_norm = DataType(0); + for (std::int64_t i = 0; i < k; ++i) { + std::int64_t xidx = isRowMajor ? i + midx * k : i * m + midx; + std::int64_t yidx = isRowMajor ? i + nidx * k : i * n + nidx; + auto a = x[xidx]; + auto b = y[yidx]; + a_norm += a; + b_norm += b; + a_sq_norm += (a * a); + b_sq_norm += (b * b); + acc += (a * b); + } + + auto numer = k * acc - (a_norm * b_norm); + auto Q_denom = k * a_sq_norm - (a_norm * a_norm); + auto R_denom = k * b_sq_norm - (b_norm * b_norm); + + acc = 1 - (numer / raft::sqrt(Q_denom * R_denom)); + + std::int64_t outidx = isRowMajor ? midx * n + nidx : midx + m * nidx; + dist[outidx] = acc; +} + +template +void naiveDistance(DataType* dist, + const DataType* x, + const DataType* y, + std::int64_t m, + std::int64_t n, + std::int64_t k, + cuvs::distance::DistanceType type, + bool isRowMajor, + DataType metric_arg = 2.0f, + cudaStream_t stream = 0) +{ + static const dim3 TPB(4, 256, 1); + dim3 nblks(raft::ceildiv(m, (std::int64_t)TPB.x), raft::ceildiv(n, (std::int64_t)TPB.y), 1); + + switch (type) { + case cuvs::distance::DistanceType::Canberra: + case cuvs::distance::DistanceType::Linf: + case cuvs::distance::DistanceType::L1: + naiveL1_Linf_CanberraDistanceKernel + <<>>(dist, x, y, m, n, k, type, isRowMajor); + break; + case cuvs::distance::DistanceType::L2SqrtUnexpanded: + case cuvs::distance::DistanceType::L2Unexpanded: + case cuvs::distance::DistanceType::L2SqrtExpanded: + case cuvs::distance::DistanceType::L2Expanded: + naiveDistanceKernel + <<>>(dist, x, y, m, n, k, type, isRowMajor); + break; + case cuvs::distance::DistanceType::CosineExpanded: + naiveCosineDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor); + break; + case cuvs::distance::DistanceType::HellingerExpanded: + naiveHellingerDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor); + break; + case cuvs::distance::DistanceType::LpUnexpanded: + naiveLpUnexpDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor, metric_arg); + break; + case cuvs::distance::DistanceType::HammingUnexpanded: + naiveHammingDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor); + break; + case cuvs::distance::DistanceType::InnerProduct: + naiveInnerProductKernel<<>>(dist, x, y, m, n, k, isRowMajor); + break; + case cuvs::distance::DistanceType::JensenShannon: + naiveJensenShannonDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor); + break; + case cuvs::distance::DistanceType::RusselRaoExpanded: + naiveRussellRaoDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor); + break; + case cuvs::distance::DistanceType::KLDivergence: + naiveKLDivergenceDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor); + break; + case cuvs::distance::DistanceType::CorrelationExpanded: + naiveCorrelationDistanceKernel + <<>>(dist, x, y, m, n, k, isRowMajor); + break; + default: FAIL() << "should be here\n"; + } + RAFT_CUDA_TRY(cudaPeekAtLastError()); +} + +template +struct DistanceInputs { + DataType tolerance; + std::int64_t m, n, k; + bool isRowMajor; + unsigned long long int seed; + DataType metric_arg = 2.0f; +}; + +template +::std::ostream& operator<<(::std::ostream& os, const DistanceInputs& dims) +{ + return os; +} + +// TODO: Remove when mdspan-based raft::runtime::distance::pairwise_distance is +// implemented. +// +// Context: +// https://github.com/rapidsai/raft/issues/1338 +template +constexpr bool layout_to_row_major(); + +template <> +constexpr bool layout_to_row_major() +{ + return true; +} +template <> +constexpr bool layout_to_row_major() +{ + return false; +} + +template +void distanceLauncher(raft::resources const& handle, + DataType* x, + DataType* y, + DataType* dist, + DataType* dist2, + std::int64_t m, + std::int64_t n, + std::int64_t k, + DistanceInputs& params, + DataType threshold, + DataType metric_arg = 2.0f) +{ + auto x_v = raft::make_device_matrix_view(x, m, k); + auto y_v = raft::make_device_matrix_view(y, n, k); + auto dist_v = raft::make_device_matrix_view(dist, m, n); + + cuvs::distance::pairwise_distance(handle, x_v, y_v, dist_v, distanceType, metric_arg); +} + +template +class DistanceTest : public ::testing::TestWithParam> { + public: + DistanceTest() + : params(::testing::TestWithParam>::GetParam()), + stream(raft::resource::get_cuda_stream(handle)), + x(params.m * params.k, stream), + y(params.n * params.k, stream), + dist_ref(params.m * params.n, stream), + dist(params.m * params.n, stream), + dist2(params.m * params.n, stream) + { + } + + void SetUp() override + { + auto testInfo = testing::UnitTest::GetInstance()->current_test_info(); + raft::common::nvtx::range fun_scope( + "test::%s/%s", testInfo->test_suite_name(), testInfo->name()); + + raft::random::RngState r(params.seed); + std::int64_t m = params.m; + std::int64_t n = params.n; + std::int64_t k = params.k; + DataType metric_arg = params.metric_arg; + bool isRowMajor = params.isRowMajor; + if (distanceType == cuvs::distance::DistanceType::HellingerExpanded || + distanceType == cuvs::distance::DistanceType::JensenShannon || + distanceType == cuvs::distance::DistanceType::KLDivergence) { + // Hellinger works only on positive numbers + uniform(handle, r, x.data(), m * k, DataType(0.0), DataType(1.0)); + uniform(handle, r, y.data(), n * k, DataType(0.0), DataType(1.0)); + } else if (distanceType == cuvs::distance::DistanceType::RusselRaoExpanded) { + uniform(handle, r, x.data(), m * k, DataType(0.0), DataType(1.0)); + uniform(handle, r, y.data(), n * k, DataType(0.0), DataType(1.0)); + // Russel rao works on boolean values. + bernoulli(handle, r, x.data(), m * k, 0.5f); + bernoulli(handle, r, y.data(), n * k, 0.5f); + } else { + uniform(handle, r, x.data(), m * k, DataType(-1.0), DataType(1.0)); + uniform(handle, r, y.data(), n * k, DataType(-1.0), DataType(1.0)); + } + naiveDistance( + dist_ref.data(), x.data(), y.data(), m, n, k, distanceType, isRowMajor, metric_arg, stream); + + DataType threshold = -10000.f; + + if (isRowMajor) { + distanceLauncher(handle, + x.data(), + y.data(), + dist.data(), + dist2.data(), + m, + n, + k, + params, + threshold, + metric_arg); + + } else { + distanceLauncher(handle, + x.data(), + y.data(), + dist.data(), + dist2.data(), + m, + n, + k, + params, + threshold, + metric_arg); + } + raft::resource::sync_stream(handle, stream); + } + + protected: + raft::resources handle; + cudaStream_t stream; + + DistanceInputs params; + rmm::device_uvector x, y, dist_ref, dist, dist2; +}; + +/* + * This test suite verifies the path when X and Y are same buffer, + * distance metrics which requires norms like L2 expanded/cosine/correlation + * takes a more optimal path in such case to skip norm calculation for Y buffer. + * It may happen that though both X and Y are same buffer but user passes + * different dimensions for them like in case of tiled_brute_force_knn. + */ +template +class DistanceTestSameBuffer : public ::testing::TestWithParam> { + public: + using dev_vector = rmm::device_uvector; + DistanceTestSameBuffer() + : params(::testing::TestWithParam>::GetParam()), + stream(raft::resource::get_cuda_stream(handle)), + x(params.m * params.k, stream), + dist_ref({dev_vector(params.m * params.m, stream), dev_vector(params.m * params.m, stream)}), + dist({dev_vector(params.m * params.m, stream), dev_vector(params.m * params.m, stream)}), + dist2({dev_vector(params.m * params.m, stream), dev_vector(params.m * params.m, stream)}) + { + } + + void SetUp() override + { + auto testInfo = testing::UnitTest::GetInstance()->current_test_info(); + raft::common::nvtx::range fun_scope( + "test::%s/%s", testInfo->test_suite_name(), testInfo->name()); + + raft::random::RngState r(params.seed); + std::int64_t m = params.m; + std::int64_t n = params.m; + std::int64_t k = params.k; + DataType metric_arg = params.metric_arg; + bool isRowMajor = params.isRowMajor; + if (distanceType == cuvs::distance::DistanceType::HellingerExpanded || + distanceType == cuvs::distance::DistanceType::JensenShannon || + distanceType == cuvs::distance::DistanceType::KLDivergence) { + // Hellinger works only on positive numbers + uniform(handle, r, x.data(), m * k, DataType(0.0), DataType(1.0)); + } else if (distanceType == cuvs::distance::DistanceType::RusselRaoExpanded) { + uniform(handle, r, x.data(), m * k, DataType(0.0), DataType(1.0)); + // Russel rao works on boolean values. + bernoulli(handle, r, x.data(), m * k, 0.5f); + } else { + uniform(handle, r, x.data(), m * k, DataType(-1.0), DataType(1.0)); + } + + for (std::int64_t i = 0; i < 2; i++) { + // both X and Y are same buffer but when i = 1 + // different dimensions for x & y is passed. + m = m / (i + 1); + naiveDistance(dist_ref[i].data(), + x.data(), + x.data(), + m, + n, + k, + distanceType, + isRowMajor, + metric_arg, + stream); + + DataType threshold = -10000.f; + + if (isRowMajor) { + distanceLauncher(handle, + x.data(), + x.data(), + dist[i].data(), + dist2[i].data(), + m, + n, + k, + params, + threshold, + metric_arg); + + } else { + distanceLauncher(handle, + x.data(), + x.data(), + dist[i].data(), + dist2[i].data(), + m, + n, + k, + params, + threshold, + metric_arg); + } + } + raft::resource::sync_stream(handle, stream); + } + + protected: + raft::resources handle; + cudaStream_t stream; + + DistanceInputs params; + dev_vector x; + static const std::int64_t N = 2; + std::array dist_ref, dist, dist2; +}; + +template +class BigMatrixDistanceTest : public ::testing::Test { + public: + BigMatrixDistanceTest() + : x(m * k, raft::resource::get_cuda_stream(handle)), + dist(std::size_t(m) * m, raft::resource::get_cuda_stream(handle)){}; + void SetUp() override + { + auto testInfo = testing::UnitTest::GetInstance()->current_test_info(); + raft::common::nvtx::range fun_scope( + "test::%s/%s", testInfo->test_suite_name(), testInfo->name()); + + constexpr float metric_arg = 0.0f; + auto x_v = + raft::make_device_matrix_view(x.data(), m, k); + auto dist_v = raft::make_device_matrix_view( + dist.data(), m, n); + + cuvs::distance::pairwise_distance(handle, x_v, x_v, dist_v, distanceType, metric_arg); + raft::resource::sync_stream(handle); + } + + protected: + raft::resources handle; + std::int64_t m = 48000; + std::int64_t n = 48000; + std::int64_t k = 1; + rmm::device_uvector x, dist; +}; +} // end namespace distance +} // namespace cuvs diff --git a/cpp/test/neighbors/ann_ivf_flat_c.cu b/cpp/test/neighbors/ann_ivf_flat_c.cu index e854504948..7844188603 100644 --- a/cpp/test/neighbors/ann_ivf_flat_c.cu +++ b/cpp/test/neighbors/ann_ivf_flat_c.cu @@ -30,7 +30,7 @@ extern "C" void run_ivf_flat(int64_t n_rows, float* query_data, float* distances_data, int64_t* neighbors_data, - enum DistanceType metric, + cuvsDistanceType metric, size_t n_probes, size_t n_lists); @@ -51,7 +51,7 @@ void recall_eval(T* query_data, size_t n_rows, size_t n_dim, size_t n_neighbors, - DistanceType metric, + cuvsDistanceType metric, size_t n_probes, size_t n_lists) { @@ -101,9 +101,9 @@ TEST(IvfFlatC, BuildSearch) int64_t n_dim = 32; uint32_t n_neighbors = 8; - enum DistanceType metric = L2Expanded; - size_t n_probes = 20; - size_t n_lists = 1024; + cuvsDistanceType metric = L2Expanded; + size_t n_probes = 20; + size_t n_lists = 1024; float *index_data, *query_data, *distances_data; int64_t* neighbors_data; diff --git a/cpp/test/neighbors/ann_ivf_pq_c.cu b/cpp/test/neighbors/ann_ivf_pq_c.cu index 94d121ce2f..88cd1bd93a 100644 --- a/cpp/test/neighbors/ann_ivf_pq_c.cu +++ b/cpp/test/neighbors/ann_ivf_pq_c.cu @@ -30,7 +30,7 @@ extern "C" void run_ivf_pq(int64_t n_rows, float* query_data, float* distances_data, int64_t* neighbors_data, - enum DistanceType metric, + cuvsDistanceType metric, size_t n_probes, size_t n_lists); @@ -51,7 +51,7 @@ void recall_eval(T* query_data, size_t n_rows, size_t n_dim, size_t n_neighbors, - DistanceType metric, + cuvsDistanceType metric, size_t n_probes, size_t n_lists) { @@ -101,9 +101,9 @@ TEST(IvfPqC, BuildSearch) int64_t n_dim = 32; uint32_t n_neighbors = 8; - enum DistanceType metric = L2Expanded; - size_t n_probes = 20; - size_t n_lists = 1024; + cuvsDistanceType metric = L2Expanded; + size_t n_probes = 20; + size_t n_lists = 1024; float *index_data, *query_data, *distances_data; int64_t* neighbors_data; diff --git a/cpp/test/neighbors/ann_utils.cuh b/cpp/test/neighbors/ann_utils.cuh index 079740945f..27a0fff7fd 100644 --- a/cpp/test/neighbors/ann_utils.cuh +++ b/cpp/test/neighbors/ann_utils.cuh @@ -83,27 +83,35 @@ struct print_metric { inline auto operator<<(std::ostream& os, const print_metric& p) -> std::ostream& { switch (p.value) { - case cuvs::distance::L2Expanded: os << "distance::L2Expanded"; break; - case cuvs::distance::L2SqrtExpanded: os << "distance::L2SqrtExpanded"; break; - case cuvs::distance::CosineExpanded: os << "distance::CosineExpanded"; break; - case cuvs::distance::L1: os << "distance::L1"; break; - case cuvs::distance::L2Unexpanded: os << "distance::L2Unexpanded"; break; - case cuvs::distance::L2SqrtUnexpanded: os << "distance::L2SqrtUnexpanded"; break; - case cuvs::distance::InnerProduct: os << "distance::InnerProduct"; break; - case cuvs::distance::Linf: os << "distance::Linf"; break; - case cuvs::distance::Canberra: os << "distance::Canberra"; break; - case cuvs::distance::LpUnexpanded: os << "distance::LpUnexpanded"; break; - case cuvs::distance::CorrelationExpanded: os << "distance::CorrelationExpanded"; break; - case cuvs::distance::JaccardExpanded: os << "distance::JaccardExpanded"; break; - case cuvs::distance::HellingerExpanded: os << "distance::HellingerExpanded"; break; - case cuvs::distance::Haversine: os << "distance::Haversine"; break; - case cuvs::distance::BrayCurtis: os << "distance::BrayCurtis"; break; - case cuvs::distance::JensenShannon: os << "distance::JensenShannon"; break; - case cuvs::distance::HammingUnexpanded: os << "distance::HammingUnexpanded"; break; - case cuvs::distance::KLDivergence: os << "distance::KLDivergence"; break; - case cuvs::distance::RusselRaoExpanded: os << "distance::RusselRaoExpanded"; break; - case cuvs::distance::DiceExpanded: os << "distance::DiceExpanded"; break; - case cuvs::distance::Precomputed: os << "distance::Precomputed"; break; + case cuvs::distance::DistanceType::L2Expanded: os << "distance::L2Expanded"; break; + case cuvs::distance::DistanceType::L2SqrtExpanded: os << "distance::L2SqrtExpanded"; break; + case cuvs::distance::DistanceType::CosineExpanded: os << "distance::CosineExpanded"; break; + case cuvs::distance::DistanceType::L1: os << "distance::L1"; break; + case cuvs::distance::DistanceType::L2Unexpanded: os << "distance::L2Unexpanded"; break; + case cuvs::distance::DistanceType::L2SqrtUnexpanded: os << "distance::L2SqrtUnexpanded"; break; + case cuvs::distance::DistanceType::InnerProduct: os << "distance::InnerProduct"; break; + case cuvs::distance::DistanceType::Linf: os << "distance::Linf"; break; + case cuvs::distance::DistanceType::Canberra: os << "distance::Canberra"; break; + case cuvs::distance::DistanceType::LpUnexpanded: os << "distance::LpUnexpanded"; break; + case cuvs::distance::DistanceType::CorrelationExpanded: + os << "distance::CorrelationExpanded"; + break; + case cuvs::distance::DistanceType::JaccardExpanded: os << "distance::JaccardExpanded"; break; + case cuvs::distance::DistanceType::HellingerExpanded: + os << "distance::HellingerExpanded"; + break; + case cuvs::distance::DistanceType::Haversine: os << "distance::Haversine"; break; + case cuvs::distance::DistanceType::BrayCurtis: os << "distance::BrayCurtis"; break; + case cuvs::distance::DistanceType::JensenShannon: os << "distance::JensenShannon"; break; + case cuvs::distance::DistanceType::HammingUnexpanded: + os << "distance::HammingUnexpanded"; + break; + case cuvs::distance::DistanceType::KLDivergence: os << "distance::KLDivergence"; break; + case cuvs::distance::DistanceType::RusselRaoExpanded: + os << "distance::RusselRaoExpanded"; + break; + case cuvs::distance::DistanceType::DiceExpanded: os << "distance::DiceExpanded"; break; + case cuvs::distance::DistanceType::Precomputed: os << "distance::Precomputed"; break; default: RAFT_FAIL("unreachable code"); } return os; diff --git a/cpp/test/neighbors/brute_force_c.cu b/cpp/test/neighbors/brute_force_c.cu index 7730a98c63..8caf1c9d1c 100644 --- a/cpp/test/neighbors/brute_force_c.cu +++ b/cpp/test/neighbors/brute_force_c.cu @@ -30,7 +30,7 @@ extern "C" void run_brute_force(int64_t n_rows, float* query_data, float* distances_data, int64_t* neighbors_data, - enum DistanceType metric); + cuvsDistanceType metric); template void generate_random_data(T* devPtr, size_t size) @@ -49,7 +49,7 @@ void recall_eval(T* query_data, size_t n_rows, size_t n_dim, size_t n_neighbors, - DistanceType metric) + cuvsDistanceType metric) { raft::handle_t handle; auto distances_ref = raft::make_device_matrix(handle, n_queries, n_neighbors); @@ -97,7 +97,7 @@ TEST(BruteForceC, BuildSearch) int64_t n_dim = 32; uint32_t n_neighbors = 8; - enum DistanceType metric = L2Expanded; + cuvsDistanceType metric = L2Expanded; float *index_data, *query_data, *distances_data; int64_t* neighbors_data; diff --git a/cpp/test/neighbors/run_brute_force_c.c b/cpp/test/neighbors/run_brute_force_c.c index ed775a2d6c..ed9e999709 100644 --- a/cpp/test/neighbors/run_brute_force_c.c +++ b/cpp/test/neighbors/run_brute_force_c.c @@ -24,7 +24,7 @@ void run_brute_force(int64_t n_rows, float* query_data, float* distances_data, int64_t* neighbors_data, - enum DistanceType metric) + cuvsDistanceType metric) { // create cuvsResources_t cuvsResources_t res; diff --git a/cpp/test/neighbors/run_ivf_flat_c.c b/cpp/test/neighbors/run_ivf_flat_c.c index badb507a50..9ecbd18eb2 100644 --- a/cpp/test/neighbors/run_ivf_flat_c.c +++ b/cpp/test/neighbors/run_ivf_flat_c.c @@ -24,7 +24,7 @@ void run_ivf_flat(int64_t n_rows, float* query_data, float* distances_data, int64_t* neighbors_data, - enum DistanceType metric, + cuvsDistanceType metric, size_t n_probes, size_t n_lists) { diff --git a/cpp/test/neighbors/run_ivf_pq_c.c b/cpp/test/neighbors/run_ivf_pq_c.c index fece4a644c..332c8a0f88 100644 --- a/cpp/test/neighbors/run_ivf_pq_c.c +++ b/cpp/test/neighbors/run_ivf_pq_c.c @@ -24,7 +24,7 @@ void run_ivf_pq(int64_t n_rows, float* query_data, float* distances_data, int64_t* neighbors_data, - enum DistanceType metric, + cuvsDistanceType metric, size_t n_probes, size_t n_lists) { diff --git a/dependencies.yaml b/dependencies.yaml index eb1f99d4a1..0c72b9b9ac 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -220,6 +220,7 @@ dependencies: - cuda-nvtx-dev - cuda-cudart-dev - cuda-profiler-api + - libnvjitlink - libcublas-dev - libcurand-dev - libcusolver-dev diff --git a/docs/source/cpp_api/distance.rst b/docs/source/cpp_api/distance.rst index 21f4558ecd..c1b8c619d1 100644 --- a/docs/source/cpp_api/distance.rst +++ b/docs/source/cpp_api/distance.rst @@ -17,3 +17,16 @@ namespace *cuvs::distance* .. doxygenenum:: cuvs::distance::DistanceType :project: cuvs + + +Pairwise Distances +------------------ + +``include `` + +namespace *cuvs::distance* + +.. doxygengroup:: pairwise_distance + :project: cuvs + :members: + :content-only: \ No newline at end of file diff --git a/python/cuvs/cuvs/distance_type.pxd b/python/cuvs/cuvs/distance_type.pxd index a1f0366a56..b85ee4b366 100644 --- a/python/cuvs/cuvs/distance_type.pxd +++ b/python/cuvs/cuvs/distance_type.pxd @@ -17,7 +17,7 @@ cdef extern from "cuvs/distance/distance_types.h" nogil: - ctypedef enum DistanceType: + ctypedef enum cuvsDistanceType: L2Expanded L2SqrtExpanded CosineExpanded diff --git a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd index 77e484fba2..c57fa9e8da 100644 --- a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd +++ b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pxd @@ -19,7 +19,7 @@ from libc.stdint cimport uintptr_t from cuvs.common.c_api cimport cuvsError_t, cuvsResources_t from cuvs.common.cydlpack cimport DLDataType, DLManagedTensor -from cuvs.distance_type cimport DistanceType +from cuvs.distance_type cimport cuvsDistanceType cdef extern from "cuvs/neighbors/brute_force.h" nogil: @@ -36,7 +36,7 @@ cdef extern from "cuvs/neighbors/brute_force.h" nogil: cuvsError_t cuvsBruteForceBuild(cuvsResources_t res, DLManagedTensor* dataset, - DistanceType metric, + cuvsDistanceType metric, float metric_arg, cuvsBruteForceIndex_t index) except + diff --git a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx index ccb10e3055..6af3c920cf 100644 --- a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx +++ b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx @@ -26,7 +26,7 @@ from libc.stdint cimport uint32_t from libcpp cimport bool from cuvs.common cimport cydlpack -from cuvs.distance_type cimport DistanceType +from cuvs.distance_type cimport cuvsDistanceType from pylibraft.common import auto_convert_output, cai_wrapper, device_ndarray from pylibraft.common.cai_wrapper import wrap_array @@ -105,7 +105,7 @@ def build(dataset, metric="sqeuclidean", metric_arg=2.0, resources=None): cdef cuvsResources_t res = resources.get_c_obj() - cdef DistanceType c_metric = DISTANCE_TYPES[metric] + cdef cuvsDistanceType c_metric = DISTANCE_TYPES[metric] cdef Index idx = Index() cdef cydlpack.DLManagedTensor* dataset_dlpack = \ cydlpack.dlpack_c(dataset_ai) diff --git a/rust/cuvs/src/distance_type.rs b/rust/cuvs/src/distance_type.rs index 4ac3e9164b..a0cbcc86e3 100644 --- a/rust/cuvs/src/distance_type.rs +++ b/rust/cuvs/src/distance_type.rs @@ -14,4 +14,4 @@ * limitations under the License. */ -pub type DistanceType = ffi::DistanceType; +pub type DistanceType = ffi::cuvsDistanceType; From 6399d05e836337796ad1547a760ba16ddbcd0d48 Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Mon, 29 Apr 2024 23:14:04 +0200 Subject: [PATCH 043/623] Remove libnvjitlink dependency. (#97) PR #85 added a dependency on `libnvjitlink`. This was needed only temporarily, due to an issue with a particular build of `cupy` that caused an undesired conda environment. This should be safe to remove. Authors: - Bradley Dice (https://github.com/bdice) Approvers: - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cuvs/pull/97 --- conda/environments/all_cuda-122_arch-aarch64.yaml | 1 - conda/environments/all_cuda-122_arch-x86_64.yaml | 1 - dependencies.yaml | 1 - 3 files changed, 3 deletions(-) diff --git a/conda/environments/all_cuda-122_arch-aarch64.yaml b/conda/environments/all_cuda-122_arch-aarch64.yaml index 3c53191605..7b50ceb0a0 100644 --- a/conda/environments/all_cuda-122_arch-aarch64.yaml +++ b/conda/environments/all_cuda-122_arch-aarch64.yaml @@ -32,7 +32,6 @@ dependencies: - libcurand-dev - libcusolver-dev - libcusparse-dev -- libnvjitlink - make - nccl>=2.9.9 - ninja diff --git a/conda/environments/all_cuda-122_arch-x86_64.yaml b/conda/environments/all_cuda-122_arch-x86_64.yaml index 4e84731c72..6c933e193d 100644 --- a/conda/environments/all_cuda-122_arch-x86_64.yaml +++ b/conda/environments/all_cuda-122_arch-x86_64.yaml @@ -32,7 +32,6 @@ dependencies: - libcurand-dev - libcusolver-dev - libcusparse-dev -- libnvjitlink - make - nccl>=2.9.9 - ninja diff --git a/dependencies.yaml b/dependencies.yaml index 0c72b9b9ac..eb1f99d4a1 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -220,7 +220,6 @@ dependencies: - cuda-nvtx-dev - cuda-cudart-dev - cuda-profiler-api - - libnvjitlink - libcublas-dev - libcurand-dev - libcusolver-dev From 82de6ef1982b59637b36af5617ffdcafc47ccb0d Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 30 Apr 2024 13:51:53 -0700 Subject: [PATCH 044/623] Add python and rust bindings for Ivf-Pq (#90) Authors: - Ben Frederickson (https://github.com/benfred) Approvers: - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/90 --- cpp/include/cuvs/neighbors/ivf_flat.h | 2 +- cpp/include/cuvs/neighbors/ivf_pq.h | 50 +- cpp/src/neighbors/ivf_pq_c.cpp | 62 +-- cpp/test/neighbors/run_ivf_pq_c.c | 8 +- docs/source/python_api/neighbors.rst | 4 +- .../python_api/neighbors_brute_force.rst | 22 + docs/source/python_api/neighbors_ivf_pq.rst | 34 ++ python/cuvs/cuvs/neighbors/CMakeLists.txt | 1 + python/cuvs/cuvs/neighbors/__init__.py | 4 +- .../neighbors/brute_force/brute_force.pyx | 4 +- python/cuvs/cuvs/neighbors/cagra/cagra.pyx | 2 +- .../cuvs/cuvs/neighbors/ivf_pq/CMakeLists.txt | 24 + .../cuvs/cuvs/neighbors/ivf_pq/__init__.pxd | 0 python/cuvs/cuvs/neighbors/ivf_pq/__init__.py | 18 + python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pxd | 93 ++++ python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pyx | 433 ++++++++++++++++++ python/cuvs/cuvs/test/test_ivf_pq.py | 168 +++++++ rust/cuvs-sys/build.rs | 2 +- rust/cuvs-sys/cuvs_c_wrapper.h | 1 + rust/cuvs/src/brute_force.rs | 3 +- rust/cuvs/src/ivf_pq/index.rs | 165 +++++++ rust/cuvs/src/ivf_pq/index_params.rs | 176 +++++++ rust/cuvs/src/ivf_pq/mod.rs | 84 ++++ rust/cuvs/src/ivf_pq/search_params.rs | 102 +++++ rust/cuvs/src/lib.rs | 1 + 25 files changed, 1393 insertions(+), 70 deletions(-) create mode 100644 docs/source/python_api/neighbors_brute_force.rst create mode 100644 docs/source/python_api/neighbors_ivf_pq.rst create mode 100644 python/cuvs/cuvs/neighbors/ivf_pq/CMakeLists.txt create mode 100644 python/cuvs/cuvs/neighbors/ivf_pq/__init__.pxd create mode 100644 python/cuvs/cuvs/neighbors/ivf_pq/__init__.py create mode 100644 python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pxd create mode 100644 python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pyx create mode 100644 python/cuvs/cuvs/test/test_ivf_pq.py create mode 100644 rust/cuvs/src/ivf_pq/index.rs create mode 100644 rust/cuvs/src/ivf_pq/index_params.rs create mode 100644 rust/cuvs/src/ivf_pq/mod.rs create mode 100644 rust/cuvs/src/ivf_pq/search_params.rs diff --git a/cpp/include/cuvs/neighbors/ivf_flat.h b/cpp/include/cuvs/neighbors/ivf_flat.h index 22c4d361ca..07877bc75d 100644 --- a/cpp/include/cuvs/neighbors/ivf_flat.h +++ b/cpp/include/cuvs/neighbors/ivf_flat.h @@ -153,7 +153,7 @@ typedef ivfFlatIndex* cuvsIvfFlatIndex_t; * @brief Allocate IVF-Flat index * * @param[in] index cuvsIvfFlatIndex_t to allocate - * @return ivfFlatError_t + * @return cuvsError_t */ cuvsError_t ivfFlatIndexCreate(cuvsIvfFlatIndex_t* index); diff --git a/cpp/include/cuvs/neighbors/ivf_pq.h b/cpp/include/cuvs/neighbors/ivf_pq.h index 2a8269eb07..c4ebc31f4a 100644 --- a/cpp/include/cuvs/neighbors/ivf_pq.h +++ b/cpp/include/cuvs/neighbors/ivf_pq.h @@ -43,7 +43,7 @@ enum codebook_gen { // NOLINT * @brief Supplemental parameters to build IVF-PQ Index * */ -struct ivfPqIndexParams { +struct cuvsIvfPqIndexParams { /** Distance type. */ cuvsDistanceType metric; /** The argument used by some distance metrics. */ @@ -118,7 +118,7 @@ struct ivfPqIndexParams { bool conservative_memory_allocation; }; -typedef struct ivfPqIndexParams* cuvsIvfPqIndexParams_t; +typedef struct cuvsIvfPqIndexParams* cuvsIvfPqIndexParams_t; /** * @brief Allocate IVF-PQ Index params, and populate with default values @@ -147,7 +147,7 @@ cuvsError_t cuvsIvfPqIndexParamsDestroy(cuvsIvfPqIndexParams_t index_params); * @brief Supplemental parameters to search IVF-PQ index * */ -struct ivfPqSearchParams { +struct cuvsIvfPqSearchParams { /** The number of clusters to search. */ uint32_t n_probes; /** @@ -186,7 +186,7 @@ struct ivfPqSearchParams { double preferred_shmem_carveout; }; -typedef struct ivfPqSearchParams* cuvsIvfPqSearchParams_t; +typedef struct cuvsIvfPqSearchParams* cuvsIvfPqSearchParams_t; /** * @brief Allocate IVF-PQ search params, and populate with default values @@ -218,24 +218,24 @@ cuvsError_t cuvsIvfPqSearchParamsDestroy(cuvsIvfPqSearchParams_t params); typedef struct { uintptr_t addr; DLDataType dtype; -} ivfPqIndex; +} cuvsIvfPqIndex; -typedef ivfPqIndex* cuvsIvfPqIndex_t; +typedef cuvsIvfPqIndex* cuvsIvfPqIndex_t; /** * @brief Allocate IVF-PQ index * * @param[in] index cuvsIvfPqIndex_t to allocate - * @return ivfPqError_t + * @return cuvsError_t */ -cuvsError_t ivfPqIndexCreate(cuvsIvfPqIndex_t* index); +cuvsError_t cuvsIvfPqIndexCreate(cuvsIvfPqIndex_t* index); /** * @brief De-allocate IVF-PQ index * * @param[in] index cuvsIvfPqIndex_t to de-allocate */ -cuvsError_t ivfPqIndexDestroy(cuvsIvfPqIndex_t index); +cuvsError_t cuvsIvfPqIndexDestroy(cuvsIvfPqIndex_t index); /** * @} */ @@ -269,14 +269,14 @@ cuvsError_t ivfPqIndexDestroy(cuvsIvfPqIndex_t index); * * // Create IVF-PQ index * cuvsIvfPqIndex_t index; - * cuvsError_t index_create_status = ivfPqIndexCreate(&index); + * cuvsError_t index_create_status = cuvsIvfPqIndexCreate(&index); * * // Build the IVF-PQ Index - * cuvsError_t build_status = ivfPqBuild(res, index_params, &dataset, index); + * cuvsError_t build_status = cuvsIvfPqBuild(res, index_params, &dataset, index); * * // de-allocate `index_params`, `index` and `res` * cuvsError_t params_destroy_status = cuvsIvfPqIndexParamsDestroy(index_params); - * cuvsError_t index_destroy_status = ivfPqIndexDestroy(index); + * cuvsError_t index_destroy_status = cuvsIvfPqIndexDestroy(index); * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); * @endcode * @@ -286,10 +286,10 @@ cuvsError_t ivfPqIndexDestroy(cuvsIvfPqIndex_t index); * @param[out] index cuvsIvfPqIndex_t Newly built IVF-PQ index * @return cuvsError_t */ -cuvsError_t ivfPqBuild(cuvsResources_t res, - cuvsIvfPqIndexParams_t params, - DLManagedTensor* dataset, - cuvsIvfPqIndex_t index); +cuvsError_t cuvsIvfPqBuild(cuvsResources_t res, + cuvsIvfPqIndexParams_t params, + DLManagedTensor* dataset, + cuvsIvfPqIndex_t index); /** * @} */ @@ -325,8 +325,8 @@ cuvsError_t ivfPqBuild(cuvsResources_t res, * cuvsIvfPqSearchParams_t search_params; * cuvsError_t params_create_status = cuvsIvfPqSearchParamsCreate(&search_params); * - * // Search the `index` built using `ivfPqBuild` - * cuvsError_t search_status = ivfPqSearch(res, search_params, index, &queries, &neighbors, + * // Search the `index` built using `cuvsIvfPqBuild` + * cuvsError_t search_status = cuvsIvfPqSearch(res, search_params, index, &queries, &neighbors, * &distances); * * // de-allocate `search_params` and `res` @@ -336,17 +336,17 @@ cuvsError_t ivfPqBuild(cuvsResources_t res, * * @param[in] res cuvsResources_t opaque C handle * @param[in] search_params cuvsIvfPqSearchParams_t used to search IVF-PQ index - * @param[in] index ivfPqIndex which has been returned by `ivfPqBuild` + * @param[in] index cuvsIvfPqIndex which has been returned by `cuvsIvfPqBuild` * @param[in] queries DLManagedTensor* queries dataset to search * @param[out] neighbors DLManagedTensor* output `k` neighbors for queries * @param[out] distances DLManagedTensor* output `k` distances for queries */ -cuvsError_t ivfPqSearch(cuvsResources_t res, - cuvsIvfPqSearchParams_t search_params, - cuvsIvfPqIndex_t index, - DLManagedTensor* queries, - DLManagedTensor* neighbors, - DLManagedTensor* distances); +cuvsError_t cuvsIvfPqSearch(cuvsResources_t res, + cuvsIvfPqSearchParams_t search_params, + cuvsIvfPqIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances); /** * @} */ diff --git a/cpp/src/neighbors/ivf_pq_c.cpp b/cpp/src/neighbors/ivf_pq_c.cpp index 27076cbc1e..14a8791233 100644 --- a/cpp/src/neighbors/ivf_pq_c.cpp +++ b/cpp/src/neighbors/ivf_pq_c.cpp @@ -29,7 +29,7 @@ namespace { template -void* _build(cuvsResources_t res, ivfPqIndexParams params, DLManagedTensor* dataset_tensor) +void* _build(cuvsResources_t res, cuvsIvfPqIndexParams params, DLManagedTensor* dataset_tensor) { auto res_ptr = reinterpret_cast(res); @@ -62,8 +62,8 @@ void* _build(cuvsResources_t res, ivfPqIndexParams params, DLManagedTensor* data template void _search(cuvsResources_t res, - ivfPqSearchParams params, - ivfPqIndex index, + cuvsIvfPqSearchParams params, + cuvsIvfPqIndex index, DLManagedTensor* queries_tensor, DLManagedTensor* neighbors_tensor, DLManagedTensor* distances_tensor) @@ -90,17 +90,17 @@ void _search(cuvsResources_t res, } // namespace -extern "C" cuvsError_t ivfPqIndexCreate(cuvsIvfPqIndex_t* index) +extern "C" cuvsError_t cuvsIvfPqIndexCreate(cuvsIvfPqIndex_t* index) { try { - *index = new ivfPqIndex{}; + *index = new cuvsIvfPqIndex{}; return CUVS_SUCCESS; } catch (...) { return CUVS_ERROR; } } -extern "C" cuvsError_t ivfPqIndexDestroy(cuvsIvfPqIndex_t index_c_ptr) +extern "C" cuvsError_t cuvsIvfPqIndexDestroy(cuvsIvfPqIndex_t index_c_ptr) { try { auto index = *index_c_ptr; @@ -114,10 +114,10 @@ extern "C" cuvsError_t ivfPqIndexDestroy(cuvsIvfPqIndex_t index_c_ptr) } } -extern "C" cuvsError_t ivfPqBuild(cuvsResources_t res, - cuvsIvfPqIndexParams_t params, - DLManagedTensor* dataset_tensor, - cuvsIvfPqIndex_t index) +extern "C" cuvsError_t cuvsIvfPqBuild(cuvsResources_t res, + cuvsIvfPqIndexParams_t params, + DLManagedTensor* dataset_tensor, + cuvsIvfPqIndex_t index) { try { auto dataset = dataset_tensor->dl_tensor; @@ -138,12 +138,12 @@ extern "C" cuvsError_t ivfPqBuild(cuvsResources_t res, } } -extern "C" cuvsError_t ivfPqSearch(cuvsResources_t res, - cuvsIvfPqSearchParams_t params, - cuvsIvfPqIndex_t index_c_ptr, - DLManagedTensor* queries_tensor, - DLManagedTensor* neighbors_tensor, - DLManagedTensor* distances_tensor) +extern "C" cuvsError_t cuvsIvfPqSearch(cuvsResources_t res, + cuvsIvfPqSearchParams_t params, + cuvsIvfPqIndex_t index_c_ptr, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) { try { auto queries = queries_tensor->dl_tensor; @@ -184,17 +184,17 @@ extern "C" cuvsError_t ivfPqSearch(cuvsResources_t res, extern "C" cuvsError_t cuvsIvfPqIndexParamsCreate(cuvsIvfPqIndexParams_t* params) { try { - *params = new ivfPqIndexParams{.metric = L2Expanded, - .metric_arg = 2.0f, - .add_data_on_build = true, - .n_lists = 1024, - .kmeans_n_iters = 20, - .kmeans_trainset_fraction = 0.5, - .pq_bits = 8, - .pq_dim = 0, - .codebook_kind = codebook_gen::PER_SUBSPACE, - .force_random_rotation = false, - .conservative_memory_allocation = false}; + *params = new cuvsIvfPqIndexParams{.metric = L2Expanded, + .metric_arg = 2.0f, + .add_data_on_build = true, + .n_lists = 1024, + .kmeans_n_iters = 20, + .kmeans_trainset_fraction = 0.5, + .pq_bits = 8, + .pq_dim = 0, + .codebook_kind = codebook_gen::PER_SUBSPACE, + .force_random_rotation = false, + .conservative_memory_allocation = false}; return CUVS_SUCCESS; } catch (...) { return CUVS_ERROR; @@ -214,10 +214,10 @@ extern "C" cuvsError_t cuvsIvfPqIndexParamsDestroy(cuvsIvfPqIndexParams_t params extern "C" cuvsError_t cuvsIvfPqSearchParamsCreate(cuvsIvfPqSearchParams_t* params) { try { - *params = new ivfPqSearchParams{.n_probes = 20, - .lut_dtype = CUDA_R_32F, - .internal_distance_dtype = CUDA_R_32F, - .preferred_shmem_carveout = 1.0}; + *params = new cuvsIvfPqSearchParams{.n_probes = 20, + .lut_dtype = CUDA_R_32F, + .internal_distance_dtype = CUDA_R_32F, + .preferred_shmem_carveout = 1.0}; return CUVS_SUCCESS; } catch (...) { return CUVS_ERROR; diff --git a/cpp/test/neighbors/run_ivf_pq_c.c b/cpp/test/neighbors/run_ivf_pq_c.c index 332c8a0f88..b7f1444a82 100644 --- a/cpp/test/neighbors/run_ivf_pq_c.c +++ b/cpp/test/neighbors/run_ivf_pq_c.c @@ -46,14 +46,14 @@ void run_ivf_pq(int64_t n_rows, // create index cuvsIvfPqIndex_t index; - ivfPqIndexCreate(&index); + cuvsIvfPqIndexCreate(&index); // build index cuvsIvfPqIndexParams_t build_params; cuvsIvfPqIndexParamsCreate(&build_params); build_params->metric = metric; build_params->n_lists = n_lists; - ivfPqBuild(res, build_params, &dataset_tensor, index); + cuvsIvfPqBuild(res, build_params, &dataset_tensor, index); // create queries DLTensor DLManagedTensor queries_tensor; @@ -95,11 +95,11 @@ void run_ivf_pq(int64_t n_rows, cuvsIvfPqSearchParams_t search_params; cuvsIvfPqSearchParamsCreate(&search_params); search_params->n_probes = n_probes; - ivfPqSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); + cuvsIvfPqSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); // de-allocate index and res cuvsIvfPqSearchParamsDestroy(search_params); cuvsIvfPqIndexParamsDestroy(build_params); - ivfPqIndexDestroy(index); + cuvsIvfPqIndexDestroy(index); cuvsResourcesDestroy(res); } diff --git a/docs/source/python_api/neighbors.rst b/docs/source/python_api/neighbors.rst index 14e907002e..3cbbd52ea9 100644 --- a/docs/source/python_api/neighbors.rst +++ b/docs/source/python_api/neighbors.rst @@ -9,4 +9,6 @@ Nearest Neighbors :maxdepth: 2 :caption: Contents: - neighbors_cagra.rst \ No newline at end of file + neighbors_brute_force.rst + neighbors_cagra.rst + neighbors_ivf_pq.rst diff --git a/docs/source/python_api/neighbors_brute_force.rst b/docs/source/python_api/neighbors_brute_force.rst new file mode 100644 index 0000000000..5fdc3658fe --- /dev/null +++ b/docs/source/python_api/neighbors_brute_force.rst @@ -0,0 +1,22 @@ +Brute Force KNN +=============== + +.. role:: py(code) + :language: python + :class: highlight + +Index +##### + +.. autoclass:: cuvs.neighbors.brute_force.Index + :members: + +Index build +########### + +.. autofunction:: cuvs.neighbors.brute_force.build + +Index search +############ + +.. autofunction:: cuvs.neighbors.brute_force.search diff --git a/docs/source/python_api/neighbors_ivf_pq.rst b/docs/source/python_api/neighbors_ivf_pq.rst new file mode 100644 index 0000000000..e3625ba676 --- /dev/null +++ b/docs/source/python_api/neighbors_ivf_pq.rst @@ -0,0 +1,34 @@ +IVF-PQ +====== + +.. role:: py(code) + :language: python + :class: highlight + +Index build parameters +###################### + +.. autoclass:: cuvs.neighbors.ivf_pq.IndexParams + :members: + +Index search parameters +####################### + +.. autoclass:: cuvs.neighbors.ivf_pq.SearchParams + :members: + +Index +##### + +.. autoclass:: cuvs.neighbors.ivf_pq.Index + :members: + +Index build +########### + +.. autofunction:: cuvs.neighbors.ivf_pq.build + +Index search +############ + +.. autofunction:: cuvs.neighbors.ivf_pq.search diff --git a/python/cuvs/cuvs/neighbors/CMakeLists.txt b/python/cuvs/cuvs/neighbors/CMakeLists.txt index 481ec9915f..1218c3ef06 100644 --- a/python/cuvs/cuvs/neighbors/CMakeLists.txt +++ b/python/cuvs/cuvs/neighbors/CMakeLists.txt @@ -14,3 +14,4 @@ add_subdirectory(brute_force) add_subdirectory(cagra) +add_subdirectory(ivf_pq) diff --git a/python/cuvs/cuvs/neighbors/__init__.py b/python/cuvs/cuvs/neighbors/__init__.py index 1f8f956d9c..c6801973cb 100644 --- a/python/cuvs/cuvs/neighbors/__init__.py +++ b/python/cuvs/cuvs/neighbors/__init__.py @@ -13,6 +13,6 @@ # limitations under the License. -from cuvs.neighbors import cagra +from cuvs.neighbors import brute_force, cagra, ivf_pq -__all__ = ["common", "cagra"] +__all__ = ["brute_force", "cagra", "ivf_pq"] diff --git a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx index 6af3c920cf..226ee23d66 100644 --- a/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx +++ b/python/cuvs/cuvs/neighbors/brute_force/brute_force.pyx @@ -68,8 +68,6 @@ cdef class Index: def build(dataset, metric="sqeuclidean", metric_arg=2.0, resources=None): """ Build the Brute Force index from the dataset for efficient search. - The following distance metrics are supported: - - L2 Parameters ---------- @@ -167,7 +165,7 @@ def search(Index index, >>> k = 10 >>> # Using a pooling allocator reduces overhead of temporary array >>> # creation during search. This is useful if multiple searches - >>> # are performad with same query size. + >>> # are performed with same query size. >>> distances, neighbors = brute_force.search(index, queries, k) >>> neighbors = cp.asarray(neighbors) >>> distances = cp.asarray(distances) diff --git a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx index f5f94aba5e..56439dd924 100644 --- a/python/cuvs/cuvs/neighbors/cagra/cagra.pyx +++ b/python/cuvs/cuvs/neighbors/cagra/cagra.pyx @@ -512,7 +512,7 @@ def search(SearchParams search_params, ... ) >>> # Using a pooling allocator reduces overhead of temporary array >>> # creation during search. This is useful if multiple searches - >>> # are performad with same query size. + >>> # are performed with same query size. >>> distances, neighbors = cagra.search(search_params, index, queries, ... k) >>> neighbors = cp.asarray(neighbors) diff --git a/python/cuvs/cuvs/neighbors/ivf_pq/CMakeLists.txt b/python/cuvs/cuvs/neighbors/ivf_pq/CMakeLists.txt new file mode 100644 index 0000000000..97c3a18249 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_pq/CMakeLists.txt @@ -0,0 +1,24 @@ +# ============================================================================= +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +# Set the list of Cython files to build +set(cython_sources ivf_pq.pyx) +set(linked_libraries cuvs::cuvs cuvs::c_api) + +# Build all of the Cython targets +rapids_cython_create_modules( + CXX + SOURCE_FILES "${cython_sources}" + LINKED_LIBRARIES "${linked_libraries}" ASSOCIATED_TARGETS cuvs MODULE_PREFIX neighbors_pq_ +) diff --git a/python/cuvs/cuvs/neighbors/ivf_pq/__init__.pxd b/python/cuvs/cuvs/neighbors/ivf_pq/__init__.pxd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/neighbors/ivf_pq/__init__.py b/python/cuvs/cuvs/neighbors/ivf_pq/__init__.py new file mode 100644 index 0000000000..3c939aa270 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_pq/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from .ivf_pq import Index, IndexParams, SearchParams, build, search + +__all__ = ["Index", "IndexParams", "SearchParams", "build", "search"] diff --git a/python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pxd b/python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pxd new file mode 100644 index 0000000000..f036c2c611 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pxd @@ -0,0 +1,93 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +from libc.stdint cimport uint32_t, uintptr_t +from libcpp cimport bool + +from cuvs.common.c_api cimport cuvsError_t, cuvsResources_t +from cuvs.common.cydlpack cimport DLDataType, DLManagedTensor +from cuvs.distance_type cimport cuvsDistanceType + + +cdef extern from "library_types.h": + ctypedef enum cudaDataType_t: + CUDA_R_32F "CUDA_R_32F" # float + CUDA_R_16F "CUDA_R_16F" # half + + # uint8 - used to refer to IVF-PQ's fp8 storage type + CUDA_R_8U "CUDA_R_8U" + + +cdef extern from "cuvs/neighbors/ivf_pq.h" nogil: + + ctypedef enum codebook_gen: + PER_SUBSPACE + PER_CLUSTER + + ctypedef struct cuvsIvfPqIndexParams: + cuvsDistanceType metric + float metric_arg + bool add_data_on_build + uint32_t n_lists + uint32_t kmeans_n_iters + double kmeans_trainset_fraction + uint32_t pq_bits + uint32_t pq_dim + codebook_gen codebook_kind + bool force_random_rotation + bool conservative_memory_allocation + + ctypedef cuvsIvfPqIndexParams* cuvsIvfPqIndexParams_t + + ctypedef struct cuvsIvfPqSearchParams: + uint32_t n_probes + cudaDataType_t lut_dtype + cudaDataType_t internal_distance_dtype + double preferred_shmem_carveout + + ctypedef cuvsIvfPqSearchParams* cuvsIvfPqSearchParams_t + + ctypedef struct cuvsIvfPqIndex: + uintptr_t addr + DLDataType dtype + + ctypedef cuvsIvfPqIndex* cuvsIvfPqIndex_t + + cuvsError_t cuvsIvfPqIndexParamsCreate(cuvsIvfPqIndexParams_t* params) + + cuvsError_t cuvsIvfPqIndexParamsDestroy(cuvsIvfPqIndexParams_t index) + + cuvsError_t cuvsIvfPqSearchParamsCreate( + cuvsIvfPqSearchParams_t* params) + + cuvsError_t cuvsIvfPqSearchParamsDestroy(cuvsIvfPqSearchParams_t index) + + cuvsError_t cuvsIvfPqIndexCreate(cuvsIvfPqIndex_t* index) + + cuvsError_t cuvsIvfPqIndexDestroy(cuvsIvfPqIndex_t index) + + cuvsError_t cuvsIvfPqBuild(cuvsResources_t res, + cuvsIvfPqIndexParams* params, + DLManagedTensor* dataset, + cuvsIvfPqIndex_t index) except + + + cuvsError_t cuvsIvfPqSearch(cuvsResources_t res, + cuvsIvfPqSearchParams* params, + cuvsIvfPqIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances) except + diff --git a/python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pyx b/python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pyx new file mode 100644 index 0000000000..a1ca6768a9 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_pq/ivf_pq.pyx @@ -0,0 +1,433 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +import numpy as np + +cimport cuvs.common.cydlpack + +from cuvs.common.resources import auto_sync_resources + +from cython.operator cimport dereference as deref +from libcpp cimport bool, cast + +from cuvs.common cimport cydlpack +from cuvs.distance_type cimport cuvsDistanceType + +from pylibraft.common import auto_convert_output, cai_wrapper, device_ndarray +from pylibraft.common.cai_wrapper import wrap_array +from pylibraft.common.interruptible import cuda_interruptible +from pylibraft.distance.pairwise_distance import DISTANCE_TYPES +from pylibraft.neighbors.common import _check_input_array + +from libc.stdint cimport ( + int8_t, + int64_t, + uint8_t, + uint32_t, + uint64_t, + uintptr_t, +) + +from cuvs.common.exceptions import check_cuvs + + +cdef class IndexParams: + """ + Parameters to build index for IvfPq nearest neighbor search + + Parameters + ---------- + n_lists : int, default = 1024 + The number of clusters used in the coarse quantizer. + metric : str, default="sqeuclidean" + String denoting the metric type. + Valid values for metric: ["sqeuclidean", "inner_product", "euclidean"], + where: + - sqeuclidean is the euclidean distance without the square root + operation, i.e.: distance(a,b) = \\sum_i (a_i - b_i)^2, + - euclidean is the euclidean distance + - inner product distance is defined as + distance(a, b) = \\sum_i a_i * b_i. + kmeans_n_iters : int, default = 20 + The number of iterations searching for kmeans centers during index + building. + kmeans_trainset_fraction : int, default = 0.5 + If kmeans_trainset_fraction is less than 1, then the dataset is + subsampled, and only n_samples * kmeans_trainset_fraction rows + are used for training. + pq_bits : int, default = 8 + The bit length of the vector element after quantization. + pq_dim : int, default = 0 + The dimensionality of a the vector after product quantization. + When zero, an optimal value is selected using a heuristic. Note + pq_dim * pq_bits must be a multiple of 8. Hint: a smaller 'pq_dim' + results in a smaller index size and better search performance, but + lower recall. If 'pq_bits' is 8, 'pq_dim' can be set to any number, + but multiple of 8 are desirable for good performance. If 'pq_bits' + is not 8, 'pq_dim' should be a multiple of 8. For good performance, + it is desirable that 'pq_dim' is a multiple of 32. Ideally, + 'pq_dim' should be also a divisor of the dataset dim. + codebook_kind : string, default = "subspace" + Valid values ["subspace", "cluster"] + force_random_rotation : bool, default = False + Apply a random rotation matrix on the input data and queries even + if `dim % pq_dim == 0`. Note: if `dim` is not multiple of `pq_dim`, + a random rotation is always applied to the input data and queries + to transform the working space from `dim` to `rot_dim`, which may + be slightly larger than the original space and and is a multiple + of `pq_dim` (`rot_dim % pq_dim == 0`). However, this transform is + not necessary when `dim` is multiple of `pq_dim` (`dim == rot_dim`, + hence no need in adding "extra" data columns / features). By + default, if `dim == rot_dim`, the rotation transform is + initialized with the identity matrix. When + `force_random_rotation == True`, a random orthogonal transform + add_data_on_build : bool, default = True + After training the coarse and fine quantizers, we will populate + the index with the dataset if add_data_on_build == True, otherwise + the index is left empty, and the extend method can be used + to add new vectors to the index. + """ + + cdef cuvsIvfPqIndexParams* params + cdef object _metric + + def __cinit__(self): + cuvsIvfPqIndexParamsCreate(&self.params) + + def __dealloc__(self): + check_cuvs(cuvsIvfPqIndexParamsDestroy(self.params)) + + def __init__(self, *, + n_lists=1024, + metric="sqeuclidean", + metric_arg=2.0, + kmeans_n_iters=20, + kmeans_trainset_fraction=0.5, + pq_bits=8, + pq_dim=0, + codebook_kind="subspace", + force_random_rotation=False, + add_data_on_build=True, + conservative_memory_allocation=False): + self.params.n_lists = n_lists + self._metric = metric + self.params.metric = DISTANCE_TYPES[metric] + self.params.metric_arg = metric_arg + self.params.kmeans_n_iters = kmeans_n_iters + self.params.kmeans_trainset_fraction = kmeans_trainset_fraction + self.params.pq_bits = pq_bits + self.params.pq_dim = pq_dim + if codebook_kind == "subspace": + self.params.codebook_kind = codebook_gen.PER_SUBSPACE + elif codebook_kind == "cluster": + self.params.codebook_kind = codebook_gen.PER_CLUSTER + else: + raise ValueError("Incorrect codebook kind %s" % codebook_kind) + self.params.force_random_rotation = force_random_rotation + self.params.add_data_on_build = add_data_on_build + self.params.conservative_memory_allocation = \ + conservative_memory_allocation + + @property + def metric(self): + return self._metric + + @property + def metric_arg(self): + return self.params.metric_arg + + @property + def add_data_on_build(self): + return self.params.add_data_on_build + + @property + def n_lists(self): + return self.params.n_lists + + @property + def kmeans_n_iters(self): + return self.params.kmeans_n_iters + + @property + def kmeans_trainset_fraction(self): + return self.params.kmeans_trainset_fraction + + @property + def pq_bits(self): + return self.params.pq_bits + + @property + def pq_dim(self): + return self.params.pq_dim + + @property + def codebook_kind(self): + return self.params.codebook_kind + + @property + def force_random_rotation(self): + return self.params.force_random_rotation + + @property + def add_data_on_build(self): + return self.params.add_data_on_build + + @property + def conservative_memory_allocation(self): + return self.params.conservative_memory_allocation + + +cdef class Index: + """ + IvfPq index object. This object stores the trained IvfPq index state + which can be used to perform nearest neighbors searches. + """ + + cdef cuvsIvfPqIndex_t index + cdef bool trained + + def __cinit__(self): + self.trained = False + check_cuvs(cuvsIvfPqIndexCreate(&self.index)) + + def __dealloc__(self): + check_cuvs(cuvsIvfPqIndexDestroy(self.index)) + + @property + def trained(self): + return self.trained + + def __repr__(self): + return "Index(type=IvfPq)" + + +@auto_sync_resources +def build(IndexParams index_params, dataset, resources=None): + """ + Build the IvfPq index from the dataset for efficient search. + + Parameters + ---------- + index_params : :py:class:`cuvs.neighbors.ivf_pq.IndexParams` + Parameters on how to build the index + dataset : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + {resources_docstring} + + Returns + ------- + index: :py:class:`cuvs.neighbors.ivf_pq.Index` + + Examples + -------- + + >>> import cupy as cp + >>> from cuvs.neighbors import ivf_pq + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> k = 10 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> build_params = ivf_pq.IndexParams(metric="sqeuclidean") + >>> index = ivf_pq.build(build_params, dataset) + >>> distances, neighbors = ivf_pq.search(ivf_pq.SearchParams(), + ... index, dataset, + ... k) + >>> distances = cp.asarray(distances) + >>> neighbors = cp.asarray(neighbors) + """ + + dataset_ai = wrap_array(dataset) + _check_input_array(dataset_ai, [np.dtype('float32'), np.dtype('byte'), + np.dtype('ubyte')]) + + cdef Index idx = Index() + cdef cuvsError_t build_status + cdef cydlpack.DLManagedTensor* dataset_dlpack = \ + cydlpack.dlpack_c(dataset_ai) + cdef cuvsIvfPqIndexParams* params = index_params.params + + cdef cuvsResources_t res = resources.get_c_obj() + + with cuda_interruptible(): + check_cuvs(cuvsIvfPqBuild( + res, + params, + dataset_dlpack, + idx.index + )) + idx.trained = True + + return idx + + +cdef _map_dtype_np_to_cuda(dtype, supported_dtypes=None): + if supported_dtypes is not None and dtype not in supported_dtypes: + raise TypeError("Type %s is not supported" % str(dtype)) + return {np.float32: cudaDataType_t.CUDA_R_32F, + np.float16: cudaDataType_t.CUDA_R_16F, + np.uint8: cudaDataType_t.CUDA_R_8U}[dtype] + + +cdef class SearchParams: + """ + Supplemental parameters to search IVF-Pq index + + Parameters + ---------- + n_probes: int + The number of clusters to search. + lut_dtype: default = np.float32 + Data type of look up table to be created dynamically at search + time. The use of low-precision types reduces the amount of shared + memory required at search time, so fast shared memory kernels can + be used even for datasets with large dimansionality. Note that + the recall is slightly degraded when low-precision type is + selected. Possible values [np.float32, np.float16, np.uint8] + internal_distance_dtype: default = np.float32 + Storage data type for distance/similarity computation. + Possible values [np.float32, np.float16] + """ + + cdef cuvsIvfPqSearchParams* params + + def __cinit__(self): + cuvsIvfPqSearchParamsCreate(&self.params) + + def __dealloc__(self): + check_cuvs(cuvsIvfPqSearchParamsDestroy(self.params)) + + def __init__(self, *, n_probes=20, lut_dtype=np.float32, + internal_distance_dtype=np.float32): + self.params.n_probes = n_probes + self.params.lut_dtype = _map_dtype_np_to_cuda(lut_dtype) + self.params.internal_distance_dtype = \ + _map_dtype_np_to_cuda(internal_distance_dtype) + + @property + def n_probes(self): + return self.params.n_probes + + @property + def n_probes(self): + return self.params.n_probes + + @property + def lut_dtype(self): + return self.params.lut_dtype + + @property + def internal_distance_dtype(self): + return self.params.internal_distance_dtype + + +@auto_sync_resources +@auto_convert_output +def search(SearchParams search_params, + Index index, + queries, + k, + neighbors=None, + distances=None, + resources=None): + """ + Find the k nearest neighbors for each query. + + Parameters + ---------- + search_params : :py:class:`cuvs.neighbors.ivf_pq.SearchParams` + Parameters on how to search the index + index : :py:class:`cuvs.neighbors.ivf_pq.Index` + Trained IvfPq index. + queries : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + k : int + The number of neighbors. + neighbors : Optional CUDA array interface compliant matrix shape + (n_queries, k), dtype int64_t. If supplied, neighbor + indices will be written here in-place. (default None) + distances : Optional CUDA array interface compliant matrix shape + (n_queries, k) If supplied, the distances to the + neighbors will be written here in-place. (default None) + {resources_docstring} + + Examples + -------- + >>> import cupy as cp + >>> from cuvs.neighbors import ivf_pq + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> # Build the index + >>> index = ivf_pq.build(ivf_pq.IndexParams(), dataset) + >>> + >>> # Search using the built index + >>> queries = cp.random.random_sample((n_queries, n_features), + ... dtype=cp.float32) + >>> k = 10 + >>> search_params = ivf_pq.SearchParams(n_probes=20) + >>> + >>> distances, neighbors = ivf_pq.search(search_params, index, queries, + ... k) + """ + if not index.trained: + raise ValueError("Index needs to be built before calling search.") + + queries_cai = wrap_array(queries) + _check_input_array(queries_cai, [np.dtype('float32'), np.dtype('byte'), + np.dtype('ubyte')]) + + cdef uint32_t n_queries = queries_cai.shape[0] + + if neighbors is None: + neighbors = device_ndarray.empty((n_queries, k), dtype='int64') + + neighbors_cai = wrap_array(neighbors) + _check_input_array(neighbors_cai, [np.dtype('int64')], + exp_rows=n_queries, exp_cols=k) + + if distances is None: + distances = device_ndarray.empty((n_queries, k), dtype='float32') + + distances_cai = wrap_array(distances) + _check_input_array(distances_cai, [np.dtype('float32')], + exp_rows=n_queries, exp_cols=k) + + cdef cuvsIvfPqSearchParams* params = search_params.params + cdef cuvsError_t search_status + cdef cydlpack.DLManagedTensor* queries_dlpack = \ + cydlpack.dlpack_c(queries_cai) + cdef cydlpack.DLManagedTensor* neighbors_dlpack = \ + cydlpack.dlpack_c(neighbors_cai) + cdef cydlpack.DLManagedTensor* distances_dlpack = \ + cydlpack.dlpack_c(distances_cai) + cdef cuvsResources_t res = resources.get_c_obj() + + with cuda_interruptible(): + check_cuvs(cuvsIvfPqSearch( + res, + params, + index.index, + queries_dlpack, + neighbors_dlpack, + distances_dlpack + )) + + return (distances, neighbors) diff --git a/python/cuvs/cuvs/test/test_ivf_pq.py b/python/cuvs/cuvs/test/test_ivf_pq.py new file mode 100644 index 0000000000..86fdf1a23f --- /dev/null +++ b/python/cuvs/cuvs/test/test_ivf_pq.py @@ -0,0 +1,168 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from pylibraft.common import device_ndarray +from sklearn.neighbors import NearestNeighbors +from sklearn.preprocessing import normalize + +from cuvs.neighbors import ivf_pq +from cuvs.test.ann_utils import calc_recall, generate_data + + +def run_ivf_pq_build_search_test( + n_rows=10000, + n_cols=10, + n_queries=100, + k=10, + dtype=np.float32, + n_lists=100, + metric="euclidean", + pq_bits=8, + pq_dim=0, + codebook_kind="subspace", + add_data_on_build="True", + n_probes=100, + lut_dtype=np.float32, + internal_distance_dtype=np.float32, + force_random_rotation=False, + kmeans_trainset_fraction=1, + kmeans_n_iters=20, + compare=True, + inplace=True, +): + dataset = generate_data((n_rows, n_cols), dtype) + if metric == "inner_product": + dataset = normalize(dataset, norm="l2", axis=1) + dataset_device = device_ndarray(dataset) + + build_params = ivf_pq.IndexParams( + n_lists=n_lists, + metric=metric, + kmeans_n_iters=kmeans_n_iters, + kmeans_trainset_fraction=kmeans_trainset_fraction, + pq_bits=pq_bits, + pq_dim=pq_dim, + codebook_kind=codebook_kind, + force_random_rotation=force_random_rotation, + add_data_on_build=add_data_on_build, + ) + + index = ivf_pq.build(build_params, dataset_device) + + queries = generate_data((n_queries, n_cols), dtype) + out_idx = np.zeros((n_queries, k), dtype=np.int64) + out_dist = np.zeros((n_queries, k), dtype=np.float32) + + queries_device = device_ndarray(queries) + out_idx_device = device_ndarray(out_idx) if inplace else None + out_dist_device = device_ndarray(out_dist) if inplace else None + + search_params = ivf_pq.SearchParams( + n_probes=n_probes, + lut_dtype=lut_dtype, + internal_distance_dtype=internal_distance_dtype, + ) + + ret_output = ivf_pq.search( + search_params, + index, + queries_device, + k, + neighbors=out_idx_device, + distances=out_dist_device, + ) + + if not inplace: + out_dist_device, out_idx_device = ret_output + + if not compare: + return + + out_idx = out_idx_device.copy_to_host() + out_dist = out_dist_device.copy_to_host() + + # Calculate reference values with sklearn + skl_metric = { + "sqeuclidean": "sqeuclidean", + "inner_product": "cosine", + "euclidean": "euclidean", + }[metric] + nn_skl = NearestNeighbors( + n_neighbors=k, algorithm="brute", metric=skl_metric + ) + nn_skl.fit(dataset) + skl_idx = nn_skl.kneighbors(queries, return_distance=False) + + recall = calc_recall(out_idx, skl_idx) + assert recall > 0.7 + + +@pytest.mark.parametrize("inplace", [True, False]) +@pytest.mark.parametrize("dtype", [np.float32]) +@pytest.mark.parametrize( + "metric", ["sqeuclidean", "inner_product", "euclidean"] +) +def test_ivf_pq(inplace, dtype, metric): + run_ivf_pq_build_search_test( + dtype=dtype, + inplace=inplace, + metric=metric, + ) + + +@pytest.mark.parametrize( + "params", + [ + { + "k": 10, + "n_probes": 100, + "lut": np.float16, + "idd": np.float32, + }, + { + "k": 10, + "n_probes": 99, + "lut": np.uint8, + "idd": np.float32, + }, + { + "k": 10, + "n_probes": 100, + "lut": np.float16, + "idd": np.float16, + }, + { + "k": 129, + "n_probes": 100, + "lut": np.float32, + "idd": np.float32, + }, + ], +) +def test_ivf_pq_search_params(params): + run_ivf_pq_build_search_test( + n_rows=10000, + n_cols=16, + n_queries=1000, + k=params["k"], + n_lists=100, + n_probes=params["n_probes"], + metric="sqeuclidean", + dtype=np.float32, + lut_dtype=params["lut"], + internal_distance_dtype=params["idd"], + ) diff --git a/rust/cuvs-sys/build.rs b/rust/cuvs-sys/build.rs index f521f8af3f..3dcf185de2 100644 --- a/rust/cuvs-sys/build.rs +++ b/rust/cuvs-sys/build.rs @@ -100,7 +100,7 @@ fn main() { // Only generate bindings for cuvs/cagra types and functions .allowlist_type("(cuvs|bruteForce|cagra|DL).*") .allowlist_function("(cuvs|bruteForce|cagra).*") - .rustified_enum("(cuvs|cagra|DL|DistanceType).*") + .rustified_enum("(cuvs|cagra|DL|DistanceType|codebook_gen|cudaDataType_t).*") // also need some basic cuda mem functions for copying data .allowlist_function("(cudaMemcpyAsync|cudaMemcpy)") .rustified_enum("cudaError") diff --git a/rust/cuvs-sys/cuvs_c_wrapper.h b/rust/cuvs-sys/cuvs_c_wrapper.h index e2c35f12dc..d2771ed524 100644 --- a/rust/cuvs-sys/cuvs_c_wrapper.h +++ b/rust/cuvs-sys/cuvs_c_wrapper.h @@ -19,3 +19,4 @@ #include #include #include +#include diff --git a/rust/cuvs/src/brute_force.rs b/rust/cuvs/src/brute_force.rs index e2e03c2367..975a4ddc38 100644 --- a/rust/cuvs/src/brute_force.rs +++ b/rust/cuvs/src/brute_force.rs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +//! Brute Force KNN use std::io::{stderr, Write}; @@ -126,7 +127,7 @@ mod tests { Index::build(&res, metric, None, dataset).expect("failed to create brute force index"); res.sync_stream().unwrap(); - + // use the first 4 points from the dataset as queries : will test that we get them back // as their own nearest neighbor let n_queries = 4; diff --git a/rust/cuvs/src/ivf_pq/index.rs b/rust/cuvs/src/ivf_pq/index.rs new file mode 100644 index 0000000000..db214bb8eb --- /dev/null +++ b/rust/cuvs/src/ivf_pq/index.rs @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::io::{stderr, Write}; + +use crate::dlpack::ManagedTensor; +use crate::error::{check_cuvs, Result}; +use crate::ivf_pq::{IndexParams, SearchParams}; +use crate::resources::Resources; + +/// Ivf-Pq ANN Index +#[derive(Debug)] +pub struct Index(ffi::cuvsIvfPqIndex_t); + +impl Index { + /// Builds a new Index from the dataset for efficient search. + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `params` - Parameters for building the index + /// * `dataset` - A row-major matrix on either the host or device to index + pub fn build>( + res: &Resources, + params: &IndexParams, + dataset: T, + ) -> Result { + let dataset: ManagedTensor = dataset.into(); + let index = Index::new()?; + unsafe { + check_cuvs(ffi::cuvsIvfPqBuild( + res.0, + params.0, + dataset.as_ptr(), + index.0, + ))?; + } + Ok(index) + } + + /// Creates a new empty index + pub fn new() -> Result { + unsafe { + let mut index = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsIvfPqIndexCreate(index.as_mut_ptr()))?; + Ok(Index(index.assume_init())) + } + } + + /// Perform a Approximate Nearest Neighbors search on the Index + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `params` - Parameters to use in searching the index + /// * `queries` - A matrix in device memory to query for + /// * `neighbors` - Matrix in device memory that receives the indices of the nearest neighbors + /// * `distances` - Matrix in device memory that receives the distances of the nearest neighbors + pub fn search( + self, + res: &Resources, + params: &SearchParams, + queries: &ManagedTensor, + neighbors: &ManagedTensor, + distances: &ManagedTensor, + ) -> Result<()> { + unsafe { + check_cuvs(ffi::cuvsIvfPqSearch( + res.0, + params.0, + self.0, + queries.as_ptr(), + neighbors.as_ptr(), + distances.as_ptr(), + )) + } + } +} + +impl Drop for Index { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsIvfPqIndexDestroy(self.0) }) { + write!(stderr(), "failed to call cuvsIvfPqIndexDestroy {:?}", e) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ndarray::s; + use ndarray_rand::rand_distr::Uniform; + use ndarray_rand::RandomExt; + + #[test] + fn test_ivf_pq() { + let build_params = IndexParams::new().unwrap().set_n_lists(64); + + let res = Resources::new().unwrap(); + + // Create a new random dataset to index + let n_datapoints = 1024; + let n_features = 16; + let dataset = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + let dataset_device = ManagedTensor::from(&dataset).to_device(&res).unwrap(); + + // build the ivf-pq index + let index = Index::build(&res, &build_params, dataset_device) + .expect("failed to create ivf-pq index"); + + // use the first 4 points from the dataset as queries : will test that we get them back + // as their own nearest neighbor + let n_queries = 4; + let queries = dataset.slice(s![0..n_queries, ..]); + + let k = 10; + + // Ivf-Pq search API requires queries and outputs to be on device memory + // copy query data over, and allocate new device memory for the distances/ neighbors + // outputs + let queries = ManagedTensor::from(&queries).to_device(&res).unwrap(); + let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); + let neighbors = ManagedTensor::from(&neighbors_host) + .to_device(&res) + .unwrap(); + + let mut distances_host = ndarray::Array::::zeros((n_queries, k)); + let distances = ManagedTensor::from(&distances_host) + .to_device(&res) + .unwrap(); + + let search_params = SearchParams::new().unwrap(); + + index + .search(&res, &search_params, &queries, &neighbors, &distances) + .unwrap(); + + // Copy back to host memory + distances.to_host(&res, &mut distances_host).unwrap(); + neighbors.to_host(&res, &mut neighbors_host).unwrap(); + + // nearest neighbors should be themselves, since queries are from the + // dataset + assert_eq!(neighbors_host[[0, 0]], 0); + assert_eq!(neighbors_host[[1, 0]], 1); + assert_eq!(neighbors_host[[2, 0]], 2); + assert_eq!(neighbors_host[[3, 0]], 3); + } +} diff --git a/rust/cuvs/src/ivf_pq/index_params.rs b/rust/cuvs/src/ivf_pq/index_params.rs new file mode 100644 index 0000000000..e38c0945fa --- /dev/null +++ b/rust/cuvs/src/ivf_pq/index_params.rs @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::distance_type::DistanceType; +use crate::error::{check_cuvs, Result}; +use std::fmt; +use std::io::{stderr, Write}; + +pub use ffi::codebook_gen; + +pub struct IndexParams(pub ffi::cuvsIvfPqIndexParams_t); + +impl IndexParams { + /// Returns a new IndexParams + pub fn new() -> Result { + unsafe { + let mut params = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsIvfPqIndexParamsCreate(params.as_mut_ptr()))?; + Ok(IndexParams(params.assume_init())) + } + } + + /// The number of clusters used in the coarse quantizer. + pub fn set_n_lists(self, n_lists: u32) -> IndexParams { + unsafe { + (*self.0).n_lists = n_lists; + } + self + } + + /// DistanceType to use for building the index + pub fn set_metric(self, metric: DistanceType) -> IndexParams { + unsafe { + (*self.0).metric = metric; + } + self + } + + /// The number of iterations searching for kmeans centers during index building. + pub fn set_metric_arg(self, metric_arg: f32) -> IndexParams { + unsafe { + (*self.0).metric_arg = metric_arg; + } + self + } + + /// The number of iterations searching for kmeans centers during index building. + pub fn set_kmeans_n_iters(self, kmeans_n_iters: u32) -> IndexParams { + unsafe { + (*self.0).kmeans_n_iters = kmeans_n_iters; + } + self + } + + /// If kmeans_trainset_fraction is less than 1, then the dataset is + /// subsampled, and only n_samples * kmeans_trainset_fraction rows + /// are used for training. + pub fn set_kmeans_trainset_fraction(self, kmeans_trainset_fraction: f64) -> IndexParams { + unsafe { + (*self.0).kmeans_trainset_fraction = kmeans_trainset_fraction; + } + self + } + + /// The bit length of the vector element after quantization. + pub fn set_pq_bits(self, pq_bits: u32) -> IndexParams { + unsafe { + (*self.0).pq_bits = pq_bits; + } + self + } + + /// The dimensionality of a the vector after product quantization. + /// When zero, an optimal value is selected using a heuristic. Note + /// pq_dim * pq_bits must be a multiple of 8. Hint: a smaller 'pq_dim' + /// results in a smaller index size and better search performance, but + /// lower recall. If 'pq_bits' is 8, 'pq_dim' can be set to any number, + /// but multiple of 8 are desirable for good performance. If 'pq_bits' + /// is not 8, 'pq_dim' should be a multiple of 8. For good performance, + /// it is desirable that 'pq_dim' is a multiple of 32. Ideally, + /// 'pq_dim' should be also a divisor of the dataset dim. + pub fn set_pq_dim(self, pq_dim: u32) -> IndexParams { + unsafe { + (*self.0).pq_dim = pq_dim; + } + self + } + + pub fn set_codebook_kind(self, codebook_kind: codebook_gen) -> IndexParams { + unsafe { + (*self.0).codebook_kind = codebook_kind; + } + self + } + + /// Apply a random rotation matrix on the input data and queries even + /// if `dim % pq_dim == 0`. Note: if `dim` is not multiple of `pq_dim`, + /// a random rotation is always applied to the input data and queries + /// to transform the working space from `dim` to `rot_dim`, which may + /// be slightly larger than the original space and and is a multiple + /// of `pq_dim` (`rot_dim % pq_dim == 0`). However, this transform is + /// not necessary when `dim` is multiple of `pq_dim` (`dim == rot_dim`, + /// hence no need in adding "extra" data columns / features). By + /// default, if `dim == rot_dim`, the rotation transform is + /// initialized with the identity matrix. When + /// `force_random_rotation == True`, a random orthogonal transform + pub fn set_force_random_rotation(self, force_random_rotation: bool) -> IndexParams { + unsafe { + (*self.0).force_random_rotation = force_random_rotation; + } + self + } + + /// After training the coarse and fine quantizers, we will populate + /// the index with the dataset if add_data_on_build == true, otherwise + /// the index is left empty, and the extend method can be used + /// to add new vectors to the index. + pub fn set_add_data_on_build(self, add_data_on_build: bool) -> IndexParams { + unsafe { + (*self.0).add_data_on_build = add_data_on_build; + } + self + } +} + +impl fmt::Debug for IndexParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // custom debug trait here, default value will show the pointer address + // for the inner params object which isn't that useful. + write!(f, "IndexParams({:?})", unsafe { *self.0 }) + } +} + +impl Drop for IndexParams { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsIvfPqIndexParamsDestroy(self.0) }) { + write!( + stderr(), + "failed to call cuvsIvfPqIndexParamsDestroy {:?}", + e + ) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_index_params() { + let params = IndexParams::new() + .unwrap() + .set_n_lists(128) + .set_add_data_on_build(false); + + unsafe { + assert_eq!((*params.0).n_lists, 128); + assert_eq!((*params.0).add_data_on_build, false); + } + } +} diff --git a/rust/cuvs/src/ivf_pq/mod.rs b/rust/cuvs/src/ivf_pq/mod.rs new file mode 100644 index 0000000000..e101439dff --- /dev/null +++ b/rust/cuvs/src/ivf_pq/mod.rs @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//! Inverted File Product Quantization +//! +//! Example: +//! ``` +//! +//! use cuvs::ivf_pq::{Index, IndexParams, SearchParams}; +//! use cuvs::{ManagedTensor, Resources, Result}; +//! +//! use ndarray::s; +//! use ndarray_rand::rand_distr::Uniform; +//! use ndarray_rand::RandomExt; +//! +//! fn ivf_pq_example() -> Result<()> { +//! let res = Resources::new()?; +//! +//! // Create a new random dataset to index +//! let n_datapoints = 65536; +//! let n_features = 512; +//! let dataset = +//! ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); +//! +//! // build the ivf-pq index +//! let build_params = IndexParams::new()?; +//! let index = Index::build(&res, &build_params, &dataset)?; +//! println!( +//! "Indexed {}x{} datapoints into ivf-pq index", +//! n_datapoints, n_features +//! ); +//! +//! // use the first 4 points from the dataset as queries : will test that we get them back +//! // as their own nearest neighbor +//! let n_queries = 4; +//! let queries = dataset.slice(s![0..n_queries, ..]); +//! +//! let k = 10; +//! +//! // Ivf-Pq search API requires queries and outputs to be on device memory +//! // copy query data over, and allocate new device memory for the distances/ neighbors +//! // outputs +//! let queries = ManagedTensor::from(&queries).to_device(&res)?; +//! let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); +//! let neighbors = ManagedTensor::from(&neighbors_host).to_device(&res)?; +//! +//! let mut distances_host = ndarray::Array::::zeros((n_queries, k)); +//! let distances = ManagedTensor::from(&distances_host).to_device(&res)?; +//! +//! let search_params = SearchParams::new()?; +//! +//! index.search(&res, &search_params, &queries, &neighbors, &distances)?; +//! +//! // Copy back to host memory +//! distances.to_host(&res, &mut distances_host)?; +//! neighbors.to_host(&res, &mut neighbors_host)?; +//! +//! // nearest neighbors should be themselves, since queries are from the +//! // dataset +//! println!("Neighbors {:?}", neighbors_host); +//! println!("Distances {:?}", distances_host); +//! Ok(()) +//! } +//! ``` + +mod index; +mod index_params; +mod search_params; + +pub use index::Index; +pub use index_params::IndexParams; +pub use search_params::SearchParams; diff --git a/rust/cuvs/src/ivf_pq/search_params.rs b/rust/cuvs/src/ivf_pq/search_params.rs new file mode 100644 index 0000000000..b23ac490c1 --- /dev/null +++ b/rust/cuvs/src/ivf_pq/search_params.rs @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{check_cuvs, Result}; +use std::fmt; +use std::io::{stderr, Write}; + +pub use ffi::cudaDataType_t; + +/// Supplemental parameters to search IvfPq index +pub struct SearchParams(pub ffi::cuvsIvfPqSearchParams_t); + +impl SearchParams { + /// Returns a new SearchParams object + pub fn new() -> Result { + unsafe { + let mut params = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsIvfPqSearchParamsCreate(params.as_mut_ptr()))?; + Ok(SearchParams(params.assume_init())) + } + } + + /// The number of clusters to search. + pub fn set_n_probes(self, n_probes: u32) -> SearchParams { + unsafe { + (*self.0).n_probes = n_probes; + } + self + } + + /// Data type of look up table to be created dynamically at search + /// time. The use of low-precision types reduces the amount of shared + /// memory required at search time, so fast shared memory kernels can + /// be used even for datasets with large dimansionality. Note that + /// the recall is slightly degraded when low-precision type is + /// selected. + pub fn set_lut_dtype(self, lut_dtype: cudaDataType_t) -> SearchParams { + unsafe { + (*self.0).lut_dtype = lut_dtype; + } + self + } + + /// Storage data type for distance/similarity computation. + pub fn set_internal_distance_dtype( + self, + internal_distance_dtype: cudaDataType_t, + ) -> SearchParams { + unsafe { + (*self.0).internal_distance_dtype = internal_distance_dtype; + } + self + } +} + +impl fmt::Debug for SearchParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // custom debug trait here, default value will show the pointer address + // for the inner params object which isn't that useful. + write!(f, "SearchParams {{ params: {:?} }}", unsafe { *self.0 }) + } +} + +impl Drop for SearchParams { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsIvfPqSearchParamsDestroy(self.0) }) { + write!( + stderr(), + "failed to call cuvsIvfPqSearchParamsDestroy {:?}", + e + ) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search_params() { + let params = SearchParams::new().unwrap().set_n_probes(128); + + unsafe { + assert_eq!((*params.0).n_probes, 128); + } + } +} diff --git a/rust/cuvs/src/lib.rs b/rust/cuvs/src/lib.rs index de974dad7a..933b5a3828 100644 --- a/rust/cuvs/src/lib.rs +++ b/rust/cuvs/src/lib.rs @@ -23,6 +23,7 @@ pub mod cagra; pub mod distance_type; mod dlpack; mod error; +pub mod ivf_pq; mod resources; pub use dlpack::ManagedTensor; From 00616f999232a3b103361a052b7c11e233f8fdeb Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Tue, 30 Apr 2024 16:47:25 -0700 Subject: [PATCH 045/623] Add python and rust bindings for Ivf-Flat (#82) Authors: - Ben Frederickson (https://github.com/benfred) - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Dante Gama Dessavre (https://github.com/dantegd) - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/82 --- cpp/include/cuvs/neighbors/ivf_flat.h | 44 +-- cpp/src/neighbors/ivf_flat_c.cpp | 50 +-- cpp/test/neighbors/run_ivf_flat_c.c | 9 +- docs/source/python_api/neighbors.rst | 1 + docs/source/python_api/neighbors_ivf_flat.rst | 34 ++ examples/cpp/src/cagra_example.cu | 2 +- python/cuvs/cuvs/neighbors/CMakeLists.txt | 1 + python/cuvs/cuvs/neighbors/__init__.py | 4 +- .../cuvs/neighbors/ivf_flat/CMakeLists.txt | 24 ++ .../cuvs/cuvs/neighbors/ivf_flat/__init__.pxd | 0 .../cuvs/cuvs/neighbors/ivf_flat/__init__.py | 18 + .../cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pxd | 74 ++++ .../cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pyx | 359 ++++++++++++++++++ python/cuvs/cuvs/test/test_doctests.py | 2 + python/cuvs/cuvs/test/test_ivf_flat.py | 102 +++++ rust/cuvs-sys/cuvs_c_wrapper.h | 1 + rust/cuvs/src/ivf_flat/index.rs | 165 ++++++++ rust/cuvs/src/ivf_flat/index_params.rs | 124 ++++++ rust/cuvs/src/ivf_flat/mod.rs | 87 +++++ rust/cuvs/src/ivf_flat/search_params.rs | 76 ++++ rust/cuvs/src/lib.rs | 1 + 21 files changed, 1124 insertions(+), 54 deletions(-) create mode 100644 docs/source/python_api/neighbors_ivf_flat.rst create mode 100644 python/cuvs/cuvs/neighbors/ivf_flat/CMakeLists.txt create mode 100644 python/cuvs/cuvs/neighbors/ivf_flat/__init__.pxd create mode 100644 python/cuvs/cuvs/neighbors/ivf_flat/__init__.py create mode 100644 python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pxd create mode 100644 python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pyx create mode 100644 python/cuvs/cuvs/test/test_ivf_flat.py create mode 100644 rust/cuvs/src/ivf_flat/index.rs create mode 100644 rust/cuvs/src/ivf_flat/index_params.rs create mode 100644 rust/cuvs/src/ivf_flat/mod.rs create mode 100644 rust/cuvs/src/ivf_flat/search_params.rs diff --git a/cpp/include/cuvs/neighbors/ivf_flat.h b/cpp/include/cuvs/neighbors/ivf_flat.h index 07877bc75d..a6616e73e5 100644 --- a/cpp/include/cuvs/neighbors/ivf_flat.h +++ b/cpp/include/cuvs/neighbors/ivf_flat.h @@ -34,7 +34,7 @@ extern "C" { * @brief Supplemental parameters to build IVF-Flat Index * */ -struct ivfFlatIndexParams { +struct cuvsIvfFlatIndexParams { /** Distance type. */ cuvsDistanceType metric; /** The argument used by some distance metrics. */ @@ -79,7 +79,7 @@ struct ivfFlatIndexParams { bool conservative_memory_allocation; }; -typedef struct ivfFlatIndexParams* cuvsIvfFlatIndexParams_t; +typedef struct cuvsIvfFlatIndexParams* cuvsIvfFlatIndexParams_t; /** * @brief Allocate IVF-Flat Index params, and populate with default values @@ -108,12 +108,12 @@ cuvsError_t cuvsIvfFlatIndexParamsDestroy(cuvsIvfFlatIndexParams_t index_params) * @brief Supplemental parameters to search IVF-Flat index * */ -struct ivfFlatSearchParams { +struct cuvsIvfFlatSearchParams { /** The number of clusters to search. */ uint32_t n_probes; }; -typedef struct ivfFlatSearchParams* cuvsIvfFlatSearchParams_t; +typedef struct cuvsIvfFlatSearchParams* cuvsIvfFlatSearchParams_t; /** * @brief Allocate IVF-Flat search params, and populate with default values @@ -145,9 +145,9 @@ cuvsError_t cuvsIvfFlatSearchParamsDestroy(cuvsIvfFlatSearchParams_t params); typedef struct { uintptr_t addr; DLDataType dtype; -} ivfFlatIndex; +} cuvsIvfFlatIndex; -typedef ivfFlatIndex* cuvsIvfFlatIndex_t; +typedef cuvsIvfFlatIndex* cuvsIvfFlatIndex_t; /** * @brief Allocate IVF-Flat index @@ -155,14 +155,14 @@ typedef ivfFlatIndex* cuvsIvfFlatIndex_t; * @param[in] index cuvsIvfFlatIndex_t to allocate * @return cuvsError_t */ -cuvsError_t ivfFlatIndexCreate(cuvsIvfFlatIndex_t* index); +cuvsError_t cuvsIvfFlatIndexCreate(cuvsIvfFlatIndex_t* index); /** * @brief De-allocate IVF-Flat index * * @param[in] index cuvsIvfFlatIndex_t to de-allocate */ -cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index); +cuvsError_t cuvsIvfFlatIndexDestroy(cuvsIvfFlatIndex_t index); /** * @} */ @@ -196,14 +196,14 @@ cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index); * * // Create IVF-Flat index * cuvsIvfFlatIndex_t index; - * cuvsError_t index_create_status = ivfFlatIndexCreate(&index); + * cuvsError_t index_create_status = cuvsIvfFlatIndexCreate(&index); * * // Build the IVF-Flat Index - * cuvsError_t build_status = ivfFlatBuild(res, index_params, &dataset, index); + * cuvsError_t build_status = cuvsIvfFlatBuild(res, index_params, &dataset, index); * * // de-allocate `index_params`, `index` and `res` * cuvsError_t params_destroy_status = cuvsIvfFlatIndexParamsDestroy(index_params); - * cuvsError_t index_destroy_status = ivfFlatIndexDestroy(index); + * cuvsError_t index_destroy_status = cuvsIvfFlatIndexDestroy(index); * cuvsError_t res_destroy_status = cuvsResourcesDestroy(res); * @endcode * @@ -213,10 +213,10 @@ cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index); * @param[out] index cuvsIvfFlatIndex_t Newly built IVF-Flat index * @return cuvsError_t */ -cuvsError_t ivfFlatBuild(cuvsResources_t res, - cuvsIvfFlatIndexParams_t index_params, - DLManagedTensor* dataset, - cuvsIvfFlatIndex_t index); +cuvsError_t cuvsIvfFlatBuild(cuvsResources_t res, + cuvsIvfFlatIndexParams_t index_params, + DLManagedTensor* dataset, + cuvsIvfFlatIndex_t index); /** * @} */ @@ -253,7 +253,7 @@ cuvsError_t ivfFlatBuild(cuvsResources_t res, * cuvsError_t params_create_status = cuvsIvfFlatSearchParamsCreate(&search_params); * * // Search the `index` built using `ivfFlatBuild` - * cuvsError_t search_status = ivfFlatSearch(res, search_params, index, &queries, &neighbors, + * cuvsError_t search_status = cuvsIvfFlatSearch(res, search_params, index, &queries, &neighbors, * &distances); * * // de-allocate `search_params` and `res` @@ -268,12 +268,12 @@ cuvsError_t ivfFlatBuild(cuvsResources_t res, * @param[out] neighbors DLManagedTensor* output `k` neighbors for queries * @param[out] distances DLManagedTensor* output `k` distances for queries */ -cuvsError_t ivfFlatSearch(cuvsResources_t res, - cuvsIvfFlatSearchParams_t search_params, - cuvsIvfFlatIndex_t index, - DLManagedTensor* queries, - DLManagedTensor* neighbors, - DLManagedTensor* distances); +cuvsError_t cuvsIvfFlatSearch(cuvsResources_t res, + cuvsIvfFlatSearchParams_t search_params, + cuvsIvfFlatIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances); /** * @} */ diff --git a/cpp/src/neighbors/ivf_flat_c.cpp b/cpp/src/neighbors/ivf_flat_c.cpp index d8185c4f77..c79ca8df96 100644 --- a/cpp/src/neighbors/ivf_flat_c.cpp +++ b/cpp/src/neighbors/ivf_flat_c.cpp @@ -31,7 +31,7 @@ namespace { template -void* _build(cuvsResources_t res, ivfFlatIndexParams params, DLManagedTensor* dataset_tensor) +void* _build(cuvsResources_t res, cuvsIvfFlatIndexParams params, DLManagedTensor* dataset_tensor) { auto res_ptr = reinterpret_cast(res); @@ -60,8 +60,8 @@ void* _build(cuvsResources_t res, ivfFlatIndexParams params, DLManagedTensor* da template void _search(cuvsResources_t res, - ivfFlatSearchParams params, - ivfFlatIndex index, + cuvsIvfFlatSearchParams params, + cuvsIvfFlatIndex index, DLManagedTensor* queries_tensor, DLManagedTensor* neighbors_tensor, DLManagedTensor* distances_tensor) @@ -85,12 +85,12 @@ void _search(cuvsResources_t res, } // namespace -extern "C" cuvsError_t ivfFlatIndexCreate(cuvsIvfFlatIndex_t* index) +extern "C" cuvsError_t cuvsIvfFlatIndexCreate(cuvsIvfFlatIndex_t* index) { - return cuvs::core::translate_exceptions([=] { *index = new ivfFlatIndex{}; }); + return cuvs::core::translate_exceptions([=] { *index = new cuvsIvfFlatIndex{}; }); } -extern "C" cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index_c_ptr) +extern "C" cuvsError_t cuvsIvfFlatIndexDestroy(cuvsIvfFlatIndex_t index_c_ptr) { return cuvs::core::translate_exceptions([=] { auto index = *index_c_ptr; @@ -112,10 +112,10 @@ extern "C" cuvsError_t ivfFlatIndexDestroy(cuvsIvfFlatIndex_t index_c_ptr) }); } -extern "C" cuvsError_t ivfFlatBuild(cuvsResources_t res, - cuvsIvfFlatIndexParams_t params, - DLManagedTensor* dataset_tensor, - cuvsIvfFlatIndex_t index) +extern "C" cuvsError_t cuvsIvfFlatBuild(cuvsResources_t res, + cuvsIvfFlatIndexParams_t params, + DLManagedTensor* dataset_tensor, + cuvsIvfFlatIndex_t index) { return cuvs::core::translate_exceptions([=] { auto dataset = dataset_tensor->dl_tensor; @@ -140,12 +140,12 @@ extern "C" cuvsError_t ivfFlatBuild(cuvsResources_t res, }); } -extern "C" cuvsError_t ivfFlatSearch(cuvsResources_t res, - cuvsIvfFlatSearchParams_t params, - cuvsIvfFlatIndex_t index_c_ptr, - DLManagedTensor* queries_tensor, - DLManagedTensor* neighbors_tensor, - DLManagedTensor* distances_tensor) +extern "C" cuvsError_t cuvsIvfFlatSearch(cuvsResources_t res, + cuvsIvfFlatSearchParams_t params, + cuvsIvfFlatIndex_t index_c_ptr, + DLManagedTensor* queries_tensor, + DLManagedTensor* neighbors_tensor, + DLManagedTensor* distances_tensor) { return cuvs::core::translate_exceptions([=] { auto queries = queries_tensor->dl_tensor; @@ -187,14 +187,14 @@ extern "C" cuvsError_t ivfFlatSearch(cuvsResources_t res, extern "C" cuvsError_t cuvsIvfFlatIndexParamsCreate(cuvsIvfFlatIndexParams_t* params) { return cuvs::core::translate_exceptions([=] { - *params = new ivfFlatIndexParams{.metric = L2Expanded, - .metric_arg = 2.0f, - .add_data_on_build = true, - .n_lists = 1024, - .kmeans_n_iters = 20, - .kmeans_trainset_fraction = 0.5, - .adaptive_centers = false, - .conservative_memory_allocation = false}; + *params = new cuvsIvfFlatIndexParams{.metric = L2Expanded, + .metric_arg = 2.0f, + .add_data_on_build = true, + .n_lists = 1024, + .kmeans_n_iters = 20, + .kmeans_trainset_fraction = 0.5, + .adaptive_centers = false, + .conservative_memory_allocation = false}; }); } @@ -206,7 +206,7 @@ extern "C" cuvsError_t cuvsIvfFlatIndexParamsDestroy(cuvsIvfFlatIndexParams_t pa extern "C" cuvsError_t cuvsIvfFlatSearchParamsCreate(cuvsIvfFlatSearchParams_t* params) { return cuvs::core::translate_exceptions( - [=] { *params = new ivfFlatSearchParams{.n_probes = 20}; }); + [=] { *params = new cuvsIvfFlatSearchParams{.n_probes = 20}; }); } extern "C" cuvsError_t cuvsIvfFlatSearchParamsDestroy(cuvsIvfFlatSearchParams_t params) diff --git a/cpp/test/neighbors/run_ivf_flat_c.c b/cpp/test/neighbors/run_ivf_flat_c.c index 9ecbd18eb2..8cd79c91fa 100644 --- a/cpp/test/neighbors/run_ivf_flat_c.c +++ b/cpp/test/neighbors/run_ivf_flat_c.c @@ -46,14 +46,14 @@ void run_ivf_flat(int64_t n_rows, // create index cuvsIvfFlatIndex_t index; - ivfFlatIndexCreate(&index); + cuvsIvfFlatIndexCreate(&index); // build index cuvsIvfFlatIndexParams_t build_params; cuvsIvfFlatIndexParamsCreate(&build_params); build_params->metric = metric; build_params->n_lists = n_lists; - ivfFlatBuild(res, build_params, &dataset_tensor, index); + cuvsIvfFlatBuild(res, build_params, &dataset_tensor, index); // create queries DLTensor DLManagedTensor queries_tensor; @@ -95,11 +95,12 @@ void run_ivf_flat(int64_t n_rows, cuvsIvfFlatSearchParams_t search_params; cuvsIvfFlatSearchParamsCreate(&search_params); search_params->n_probes = n_probes; - ivfFlatSearch(res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); + cuvsIvfFlatSearch( + res, search_params, index, &queries_tensor, &neighbors_tensor, &distances_tensor); // de-allocate index and res cuvsIvfFlatSearchParamsDestroy(search_params); cuvsIvfFlatIndexParamsDestroy(build_params); - ivfFlatIndexDestroy(index); + cuvsIvfFlatIndexDestroy(index); cuvsResourcesDestroy(res); } diff --git a/docs/source/python_api/neighbors.rst b/docs/source/python_api/neighbors.rst index 3cbbd52ea9..022c50de34 100644 --- a/docs/source/python_api/neighbors.rst +++ b/docs/source/python_api/neighbors.rst @@ -11,4 +11,5 @@ Nearest Neighbors neighbors_brute_force.rst neighbors_cagra.rst + neighbors_ivf_flat.rst neighbors_ivf_pq.rst diff --git a/docs/source/python_api/neighbors_ivf_flat.rst b/docs/source/python_api/neighbors_ivf_flat.rst new file mode 100644 index 0000000000..5514e5e43f --- /dev/null +++ b/docs/source/python_api/neighbors_ivf_flat.rst @@ -0,0 +1,34 @@ +IVF-Flat +======== + +.. role:: py(code) + :language: python + :class: highlight + +Index build parameters +###################### + +.. autoclass:: cuvs.neighbors.ivf_flat.IndexParams + :members: + +Index search parameters +####################### + +.. autoclass:: cuvs.neighbors.ivf_flat.SearchParams + :members: + +Index +##### + +.. autoclass:: cuvs.neighbors.ivf_flat.Index + :members: + +Index build +########### + +.. autofunction:: cuvs.neighbors.ivf_flat.build + +Index search +############ + +.. autofunction:: cuvs.neighbors.ivf_flat.search diff --git a/examples/cpp/src/cagra_example.cu b/examples/cpp/src/cagra_example.cu index 6fab4291b0..630fcd4ecd 100644 --- a/examples/cpp/src/cagra_example.cu +++ b/examples/cpp/src/cagra_example.cu @@ -54,7 +54,7 @@ void cagra_build_search_simple(raft::device_resources const& dev_resources, // search K nearest neighbors cagra::search(dev_resources, search_params, index, queries, neighbors.view(), distances.view()); - // The call to ivf_flat::search is asynchronous. Before accessing the data, sync by calling + // The call to cagra::search is asynchronous. Before accessing the data, sync by calling // raft::resource::sync_stream(dev_resources); print_results(dev_resources, neighbors.view(), distances.view()); diff --git a/python/cuvs/cuvs/neighbors/CMakeLists.txt b/python/cuvs/cuvs/neighbors/CMakeLists.txt index 1218c3ef06..e0041243ab 100644 --- a/python/cuvs/cuvs/neighbors/CMakeLists.txt +++ b/python/cuvs/cuvs/neighbors/CMakeLists.txt @@ -14,4 +14,5 @@ add_subdirectory(brute_force) add_subdirectory(cagra) +add_subdirectory(ivf_flat) add_subdirectory(ivf_pq) diff --git a/python/cuvs/cuvs/neighbors/__init__.py b/python/cuvs/cuvs/neighbors/__init__.py index c6801973cb..c431ffde7b 100644 --- a/python/cuvs/cuvs/neighbors/__init__.py +++ b/python/cuvs/cuvs/neighbors/__init__.py @@ -13,6 +13,6 @@ # limitations under the License. -from cuvs.neighbors import brute_force, cagra, ivf_pq +from cuvs.neighbors import brute_force, cagra, ivf_flat, ivf_pq -__all__ = ["brute_force", "cagra", "ivf_pq"] +__all__ = ["brute_force", "cagra", "ivf_flat", "ivf_pq"] diff --git a/python/cuvs/cuvs/neighbors/ivf_flat/CMakeLists.txt b/python/cuvs/cuvs/neighbors/ivf_flat/CMakeLists.txt new file mode 100644 index 0000000000..09bd8f4227 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_flat/CMakeLists.txt @@ -0,0 +1,24 @@ +# ============================================================================= +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +# Set the list of Cython files to build +set(cython_sources ivf_flat.pyx) +set(linked_libraries cuvs::cuvs cuvs::c_api) + +# Build all of the Cython targets +rapids_cython_create_modules( + CXX + SOURCE_FILES "${cython_sources}" + LINKED_LIBRARIES "${linked_libraries}" ASSOCIATED_TARGETS cuvs MODULE_PREFIX neighbors_ivf_flat_ +) diff --git a/python/cuvs/cuvs/neighbors/ivf_flat/__init__.pxd b/python/cuvs/cuvs/neighbors/ivf_flat/__init__.pxd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/cuvs/cuvs/neighbors/ivf_flat/__init__.py b/python/cuvs/cuvs/neighbors/ivf_flat/__init__.py new file mode 100644 index 0000000000..a9c3e9b2db --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_flat/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from .ivf_flat import Index, IndexParams, SearchParams, build, search + +__all__ = ["Index", "IndexParams", "SearchParams", "build", "search"] diff --git a/python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pxd b/python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pxd new file mode 100644 index 0000000000..8859d5d969 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pxd @@ -0,0 +1,74 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +from libc.stdint cimport uint32_t, uintptr_t +from libcpp cimport bool + +from cuvs.common.c_api cimport cuvsError_t, cuvsResources_t +from cuvs.common.cydlpack cimport DLDataType, DLManagedTensor +from cuvs.distance_type cimport cuvsDistanceType + + +cdef extern from "cuvs/neighbors/ivf_flat.h" nogil: + + ctypedef struct cuvsIvfFlatIndexParams: + cuvsDistanceType metric + float metric_arg + bool add_data_on_build + uint32_t n_lists + uint32_t kmeans_n_iters + double kmeans_trainset_fraction + bool adaptive_centers + bool conservative_memory_allocation + + ctypedef cuvsIvfFlatIndexParams* cuvsIvfFlatIndexParams_t + + ctypedef struct cuvsIvfFlatSearchParams: + uint32_t n_probes + + ctypedef cuvsIvfFlatSearchParams* cuvsIvfFlatSearchParams_t + + ctypedef struct cuvsIvfFlatIndex: + uintptr_t addr + DLDataType dtype + + ctypedef cuvsIvfFlatIndex* cuvsIvfFlatIndex_t + + cuvsError_t cuvsIvfFlatIndexParamsCreate(cuvsIvfFlatIndexParams_t* params) + + cuvsError_t cuvsIvfFlatIndexParamsDestroy(cuvsIvfFlatIndexParams_t index) + + cuvsError_t cuvsIvfFlatSearchParamsCreate( + cuvsIvfFlatSearchParams_t* params) + + cuvsError_t cuvsIvfFlatSearchParamsDestroy(cuvsIvfFlatSearchParams_t index) + + cuvsError_t cuvsIvfFlatIndexCreate(cuvsIvfFlatIndex_t* index) + + cuvsError_t cuvsIvfFlatIndexDestroy(cuvsIvfFlatIndex_t index) + + cuvsError_t cuvsIvfFlatBuild(cuvsResources_t res, + cuvsIvfFlatIndexParams* params, + DLManagedTensor* dataset, + cuvsIvfFlatIndex_t index) except + + + cuvsError_t cuvsIvfFlatSearch(cuvsResources_t res, + cuvsIvfFlatSearchParams* params, + cuvsIvfFlatIndex_t index, + DLManagedTensor* queries, + DLManagedTensor* neighbors, + DLManagedTensor* distances) except + diff --git a/python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pyx b/python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pyx new file mode 100644 index 0000000000..870e9412a8 --- /dev/null +++ b/python/cuvs/cuvs/neighbors/ivf_flat/ivf_flat.pyx @@ -0,0 +1,359 @@ +# +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cython: language_level=3 + +import numpy as np + +cimport cuvs.common.cydlpack + +from cuvs.common.resources import auto_sync_resources + +from cython.operator cimport dereference as deref +from libcpp cimport bool, cast + +from cuvs.common cimport cydlpack +from cuvs.distance_type cimport cuvsDistanceType + +from pylibraft.common import auto_convert_output, cai_wrapper, device_ndarray +from pylibraft.common.cai_wrapper import wrap_array +from pylibraft.common.interruptible import cuda_interruptible +from pylibraft.distance.pairwise_distance import DISTANCE_TYPES +from pylibraft.neighbors.common import _check_input_array + +from libc.stdint cimport ( + int8_t, + int64_t, + uint8_t, + uint32_t, + uint64_t, + uintptr_t, +) + +from cuvs.common.exceptions import check_cuvs + + +cdef class IndexParams: + """ + Parameters to build index for IvfFlat nearest neighbor search + + Parameters + ---------- + n_lists : int, default = 1024 + The number of clusters used in the coarse quantizer. + metric : str, default = "sqeuclidean" + String denoting the metric type. + Valid values for metric: ["sqeuclidean", "inner_product", + "euclidean"], where + - sqeuclidean is the euclidean distance without the square root + operation, i.e.: distance(a,b) = \\sum_i (a_i - b_i)^2, + - euclidean is the euclidean distance + - inner product distance is defined as + distance(a, b) = \\sum_i a_i * b_i. + kmeans_n_iters : int, default = 20 + The number of iterations searching for kmeans centers during index + building. + The default setting is often fine, but this parameter can be decreased + to improve training time wih larger trainset fractions (10M+ vectors) + or increased for smaller trainset fractions (very small number of + vectors) to improve recall. + kmeans_trainset_fraction : int, default = 0.5 + If kmeans_trainset_fraction is less than 1, then the dataset is + subsampled, and only n_samples * kmeans_trainset_fraction rows + are used for training. + add_data_on_build : bool, default = True + After training the coarse and fine quantizers, we will populate + the index with the dataset if add_data_on_build == True, otherwise + the index is left empty, and the extend method can be used + to add new vectors to the index. + adaptive_centers : bool, default = False + By default (adaptive_centers = False), the cluster centers are + trained in `ivf_flat.build`, and and never modified in + `ivf_flat.extend`. The alternative behavior (adaptive_centers + = true) is to update the cluster centers for new data when it is + added. In this case, `index.centers()` are always exactly the + centroids of the data in the corresponding clusters. The drawback + of this behavior is that the centroids depend on the order of + adding new data (through the classification of the added data); + that is, `index.centers()` "drift" together with the changing + distribution of the newly added data. + """ + + cdef cuvsIvfFlatIndexParams* params + cdef object _metric + + def __cinit__(self): + cuvsIvfFlatIndexParamsCreate(&self.params) + + def __dealloc__(self): + check_cuvs(cuvsIvfFlatIndexParamsDestroy(self.params)) + + def __init__(self, *, + n_lists=1024, + metric="sqeuclidean", + metric_arg=2.0, + kmeans_n_iters=20, + kmeans_trainset_fraction=0.5, + adaptive_centers=False, + add_data_on_build=True, + conservative_memory_allocation=False): + self._metric = metric + self.params.metric = DISTANCE_TYPES[metric] + self.params.metric_arg = metric_arg + self.params.add_data_on_build = add_data_on_build + self.params.n_lists = n_lists + self.params.kmeans_n_iters = kmeans_n_iters + self.params.kmeans_trainset_fraction = kmeans_trainset_fraction + self.params.adaptive_centers = adaptive_centers + self.params.conservative_memory_allocation = \ + conservative_memory_allocation + + @property + def metric(self): + return self._metric + + @property + def metric_arg(self): + return self.params.metric_arg + + @property + def add_data_on_build(self): + return self.params.add_data_on_build + + @property + def n_lists(self): + return self.params.n_lists + + @property + def kmeans_n_iters(self): + return self.params.kmeans_n_iters + + @property + def kmeans_trainset_fraction(self): + return self.params.kmeans_trainset_fraction + + @property + def adaptive_centers(self): + return self.params.adaptive_centers + + @property + def conservative_memory_allocation(self): + return self.params.conservative_memory_allocation + + +cdef class Index: + """ + IvfFlat index object. This object stores the trained IvfFlat index state + which can be used to perform nearest neighbors searches. + """ + + cdef cuvsIvfFlatIndex_t index + cdef bool trained + + def __cinit__(self): + self.trained = False + check_cuvs(cuvsIvfFlatIndexCreate(&self.index)) + + def __dealloc__(self): + check_cuvs(cuvsIvfFlatIndexDestroy(self.index)) + + @property + def trained(self): + return self.trained + + def __repr__(self): + return "Index(type=IvfFlat)" + + +@auto_sync_resources +def build(IndexParams index_params, dataset, resources=None): + """ + Build the IvfFlat index from the dataset for efficient search. + + Parameters + ---------- + index_params : :py:class:`cuvs.neighbors.ivf_flat.IndexParams` + dataset : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + {resources_docstring} + + Returns + ------- + index: py:class:`cuvs.neighbors.ivf_flat.Index` + + Examples + -------- + + >>> import cupy as cp + >>> from cuvs.neighbors import ivf_flat + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> k = 10 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> build_params = ivf_flat.IndexParams(metric="sqeuclidean") + >>> index = ivf_flat.build(build_params, dataset) + >>> distances, neighbors = ivf_flat.search(ivf_flat.SearchParams(), + ... index, dataset, + ... k) + >>> distances = cp.asarray(distances) + >>> neighbors = cp.asarray(neighbors) + """ + + dataset_ai = wrap_array(dataset) + _check_input_array(dataset_ai, [np.dtype('float32'), np.dtype('byte'), + np.dtype('ubyte')]) + + cdef Index idx = Index() + cdef cuvsError_t build_status + cdef cydlpack.DLManagedTensor* dataset_dlpack = \ + cydlpack.dlpack_c(dataset_ai) + cdef cuvsIvfFlatIndexParams* params = index_params.params + + cdef cuvsResources_t res = resources.get_c_obj() + + with cuda_interruptible(): + check_cuvs(cuvsIvfFlatBuild( + res, + params, + dataset_dlpack, + idx.index + )) + idx.trained = True + + return idx + + +cdef class SearchParams: + """ + Supplemental parameters to search IVF-Flat index + + Parameters + ---------- + n_probes: int + The number of clusters to search. + """ + + cdef cuvsIvfFlatSearchParams* params + + def __cinit__(self): + cuvsIvfFlatSearchParamsCreate(&self.params) + + def __dealloc__(self): + check_cuvs(cuvsIvfFlatSearchParamsDestroy(self.params)) + + def __init__(self, *, n_probes=20): + self.params.n_probes = n_probes + + @property + def n_probes(self): + return self.params.n_probes + + +@auto_sync_resources +@auto_convert_output +def search(SearchParams search_params, + Index index, + queries, + k, + neighbors=None, + distances=None, + resources=None): + """ + Find the k nearest neighbors for each query. + + Parameters + ---------- + search_params : py:class:`cuvs.neighbors.ivf_flat.SearchParams` + index : py:class:`cuvs.neighbors.ivf_flat.Index` + Trained IvfFlat index. + queries : CUDA array interface compliant matrix shape (n_samples, dim) + Supported dtype [float, int8, uint8] + k : int + The number of neighbors. + neighbors : Optional CUDA array interface compliant matrix shape + (n_queries, k), dtype int64_t. If supplied, neighbor + indices will be written here in-place. (default None) + distances : Optional CUDA array interface compliant matrix shape + (n_queries, k) If supplied, the distances to the + neighbors will be written here in-place. (default None) + {resources_docstring} + + Examples + -------- + >>> import cupy as cp + >>> from cuvs.neighbors import ivf_flat + >>> n_samples = 50000 + >>> n_features = 50 + >>> n_queries = 1000 + >>> dataset = cp.random.random_sample((n_samples, n_features), + ... dtype=cp.float32) + >>> # Build the index + >>> index = ivf_flat.build(ivf_flat.IndexParams(), dataset) + >>> + >>> # Search using the built index + >>> queries = cp.random.random_sample((n_queries, n_features), + ... dtype=cp.float32) + >>> k = 10 + >>> search_params = ivf_flat.SearchParams(n_probes=20) + >>> + >>> distances, neighbors = ivf_flat.search(search_params, index, queries, + ... k) + """ + if not index.trained: + raise ValueError("Index needs to be built before calling search.") + + queries_cai = wrap_array(queries) + _check_input_array(queries_cai, [np.dtype('float32'), np.dtype('byte'), + np.dtype('ubyte')]) + + cdef uint32_t n_queries = queries_cai.shape[0] + + if neighbors is None: + neighbors = device_ndarray.empty((n_queries, k), dtype='int64') + + neighbors_cai = wrap_array(neighbors) + _check_input_array(neighbors_cai, [np.dtype('int64')], + exp_rows=n_queries, exp_cols=k) + + if distances is None: + distances = device_ndarray.empty((n_queries, k), dtype='float32') + + distances_cai = wrap_array(distances) + _check_input_array(distances_cai, [np.dtype('float32')], + exp_rows=n_queries, exp_cols=k) + + cdef cuvsIvfFlatSearchParams* params = search_params.params + cdef cuvsError_t search_status + cdef cydlpack.DLManagedTensor* queries_dlpack = \ + cydlpack.dlpack_c(queries_cai) + cdef cydlpack.DLManagedTensor* neighbors_dlpack = \ + cydlpack.dlpack_c(neighbors_cai) + cdef cydlpack.DLManagedTensor* distances_dlpack = \ + cydlpack.dlpack_c(distances_cai) + cdef cuvsResources_t res = resources.get_c_obj() + + with cuda_interruptible(): + check_cuvs(cuvsIvfFlatSearch( + res, + params, + index.index, + queries_dlpack, + neighbors_dlpack, + distances_dlpack + )) + + return (distances, neighbors) diff --git a/python/cuvs/cuvs/test/test_doctests.py b/python/cuvs/cuvs/test/test_doctests.py index d334aa3228..68b2861dff 100644 --- a/python/cuvs/cuvs/test/test_doctests.py +++ b/python/cuvs/cuvs/test/test_doctests.py @@ -90,6 +90,8 @@ def _find_doctests_in_obj(obj, finder=None, criteria=None): # doctests for here DOC_STRINGS = list(_find_doctests_in_obj(cuvs.neighbors)) DOC_STRINGS.extend(_find_doctests_in_obj(cuvs.neighbors.cagra)) +DOC_STRINGS.extend(_find_doctests_in_obj(cuvs.neighbors.brute_force)) +DOC_STRINGS.extend(_find_doctests_in_obj(cuvs.neighbors.ivf_flat)) DOC_STRINGS.extend(_find_doctests_in_obj(cuvs.common)) diff --git a/python/cuvs/cuvs/test/test_ivf_flat.py b/python/cuvs/cuvs/test/test_ivf_flat.py new file mode 100644 index 0000000000..81917b0e3c --- /dev/null +++ b/python/cuvs/cuvs/test/test_ivf_flat.py @@ -0,0 +1,102 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from pylibraft.common import device_ndarray +from sklearn.neighbors import NearestNeighbors +from sklearn.preprocessing import normalize + +from cuvs.neighbors import ivf_flat +from cuvs.test.ann_utils import calc_recall, generate_data + + +def run_ivf_flat_build_search_test( + n_rows=10000, + n_cols=10, + n_queries=100, + k=10, + dtype=np.float32, + metric="euclidean", + compare=True, + inplace=True, + search_params={}, +): + dataset = generate_data((n_rows, n_cols), dtype) + if metric == "inner_product": + dataset = normalize(dataset, norm="l2", axis=1) + dataset_device = device_ndarray(dataset) + + build_params = ivf_flat.IndexParams( + metric=metric, + ) + + index = ivf_flat.build(build_params, dataset_device) + + queries = generate_data((n_queries, n_cols), dtype) + out_idx = np.zeros((n_queries, k), dtype=np.int64) + out_dist = np.zeros((n_queries, k), dtype=np.float32) + + queries_device = device_ndarray(queries) + out_idx_device = device_ndarray(out_idx) if inplace else None + out_dist_device = device_ndarray(out_dist) if inplace else None + + search_params = ivf_flat.SearchParams(**search_params) + + ret_output = ivf_flat.search( + search_params, + index, + queries_device, + k, + neighbors=out_idx_device, + distances=out_dist_device, + ) + + if not inplace: + out_dist_device, out_idx_device = ret_output + + if not compare: + return + + out_idx = out_idx_device.copy_to_host() + out_dist = out_dist_device.copy_to_host() + + # Calculate reference values with sklearn + skl_metric = { + "sqeuclidean": "sqeuclidean", + "inner_product": "cosine", + "euclidean": "euclidean", + }[metric] + nn_skl = NearestNeighbors( + n_neighbors=k, algorithm="brute", metric=skl_metric + ) + nn_skl.fit(dataset) + skl_idx = nn_skl.kneighbors(queries, return_distance=False) + + recall = calc_recall(out_idx, skl_idx) + assert recall > 0.7 + + +@pytest.mark.parametrize("inplace", [True, False]) +@pytest.mark.parametrize("dtype", [np.float32]) +@pytest.mark.parametrize( + "metric", ["sqeuclidean", "inner_product", "euclidean"] +) +def test_ivf_flat(inplace, dtype, metric): + run_ivf_flat_build_search_test( + dtype=dtype, + inplace=inplace, + metric=metric, + ) diff --git a/rust/cuvs-sys/cuvs_c_wrapper.h b/rust/cuvs-sys/cuvs_c_wrapper.h index d2771ed524..c6ab02da03 100644 --- a/rust/cuvs-sys/cuvs_c_wrapper.h +++ b/rust/cuvs-sys/cuvs_c_wrapper.h @@ -18,5 +18,6 @@ // bindings for #include #include +#include #include #include diff --git a/rust/cuvs/src/ivf_flat/index.rs b/rust/cuvs/src/ivf_flat/index.rs new file mode 100644 index 0000000000..b1462a0e69 --- /dev/null +++ b/rust/cuvs/src/ivf_flat/index.rs @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::io::{stderr, Write}; + +use crate::ivf_flat::{IndexParams, SearchParams}; +use crate::dlpack::ManagedTensor; +use crate::error::{check_cuvs, Result}; +use crate::resources::Resources; + +/// Ivf-Flat ANN Index +#[derive(Debug)] +pub struct Index(ffi::cuvsIvfFlatIndex_t); + +impl Index { + /// Builds a new Index from the dataset for efficient search. + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `params` - Parameters for building the index + /// * `dataset` - A row-major matrix on either the host or device to index + pub fn build>( + res: &Resources, + params: &IndexParams, + dataset: T, + ) -> Result { + let dataset: ManagedTensor = dataset.into(); + let index = Index::new()?; + unsafe { + check_cuvs(ffi::cuvsIvfFlatBuild( + res.0, + params.0, + dataset.as_ptr(), + index.0, + ))?; + } + Ok(index) + } + + /// Creates a new empty index + pub fn new() -> Result { + unsafe { + let mut index = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsIvfFlatIndexCreate(index.as_mut_ptr()))?; + Ok(Index(index.assume_init())) + } + } + + /// Perform a Approximate Nearest Neighbors search on the Index + /// + /// # Arguments + /// + /// * `res` - Resources to use + /// * `params` - Parameters to use in searching the index + /// * `queries` - A matrix in device memory to query for + /// * `neighbors` - Matrix in device memory that receives the indices of the nearest neighbors + /// * `distances` - Matrix in device memory that receives the distances of the nearest neighbors + pub fn search( + self, + res: &Resources, + params: &SearchParams, + queries: &ManagedTensor, + neighbors: &ManagedTensor, + distances: &ManagedTensor, + ) -> Result<()> { + unsafe { + check_cuvs(ffi::cuvsIvfFlatSearch( + res.0, + params.0, + self.0, + queries.as_ptr(), + neighbors.as_ptr(), + distances.as_ptr(), + )) + } + } +} + +impl Drop for Index { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsIvfFlatIndexDestroy(self.0) }) { + write!(stderr(), "failed to call cuvsIvfFlatIndexDestroy {:?}", e) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ndarray::s; + use ndarray_rand::rand_distr::Uniform; + use ndarray_rand::RandomExt; + + #[test] + fn test_ivf_flat() { + let build_params = IndexParams::new().unwrap().set_n_lists(64); + + let res = Resources::new().unwrap(); + + // Create a new random dataset to index + let n_datapoints = 1024; + let n_features = 16; + let dataset = + ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); + + let dataset_device = ManagedTensor::from(&dataset).to_device(&res).unwrap(); + + // build the ivf-flat index + let index = + Index::build(&res, &build_params, dataset_device).expect("failed to create ivf-flat index"); + + // use the first 4 points from the dataset as queries : will test that we get them back + // as their own nearest neighbor + let n_queries = 4; + let queries = dataset.slice(s![0..n_queries, ..]); + + let k = 10; + + // IvfFlat search API requires queries and outputs to be on device memory + // copy query data over, and allocate new device memory for the distances/ neighbors + // outputs + let queries = ManagedTensor::from(&queries).to_device(&res).unwrap(); + let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); + let neighbors = ManagedTensor::from(&neighbors_host) + .to_device(&res) + .unwrap(); + + let mut distances_host = ndarray::Array::::zeros((n_queries, k)); + let distances = ManagedTensor::from(&distances_host) + .to_device(&res) + .unwrap(); + + let search_params = SearchParams::new().unwrap(); + + index + .search(&res, &search_params, &queries, &neighbors, &distances) + .unwrap(); + + // Copy back to host memory + distances.to_host(&res, &mut distances_host).unwrap(); + neighbors.to_host(&res, &mut neighbors_host).unwrap(); + + // nearest neighbors should be themselves, since queries are from the + // dataset + assert_eq!(neighbors_host[[0, 0]], 0); + assert_eq!(neighbors_host[[1, 0]], 1); + assert_eq!(neighbors_host[[2, 0]], 2); + assert_eq!(neighbors_host[[3, 0]], 3); + } +} diff --git a/rust/cuvs/src/ivf_flat/index_params.rs b/rust/cuvs/src/ivf_flat/index_params.rs new file mode 100644 index 0000000000..6c86ef527f --- /dev/null +++ b/rust/cuvs/src/ivf_flat/index_params.rs @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{check_cuvs, Result}; +use crate::distance_type::DistanceType; +use std::fmt; +use std::io::{stderr, Write}; + +pub struct IndexParams(pub ffi::cuvsIvfFlatIndexParams_t); + +impl IndexParams { + /// Returns a new IndexParams + pub fn new() -> Result { + unsafe { + let mut params = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsIvfFlatIndexParamsCreate(params.as_mut_ptr()))?; + Ok(IndexParams(params.assume_init())) + } + } + + /// The number of clusters used in the coarse quantizer. + pub fn set_n_lists(self, n_lists: u32) -> IndexParams { + unsafe { + (*self.0).n_lists = n_lists; + } + self + } + + /// DistanceType to use for building the index + pub fn set_metric(self, metric: DistanceType) -> IndexParams { + unsafe { + (*self.0).metric = metric; + } + self + } + + /// The number of iterations searching for kmeans centers during index building. + pub fn set_metric_arg(self, metric_arg: f32) -> IndexParams { + unsafe { + (*self.0).metric_arg = metric_arg; + } + self + } + /// The number of iterations searching for kmeans centers during index building. + pub fn set_kmeans_n_iters(self, kmeans_n_iters: u32) -> IndexParams { + unsafe { + (*self.0).kmeans_n_iters = kmeans_n_iters; + } + self + } + + /// If kmeans_trainset_fraction is less than 1, then the dataset is + /// subsampled, and only n_samples * kmeans_trainset_fraction rows + /// are used for training. + pub fn set_kmeans_trainset_fraction(self, kmeans_trainset_fraction: f64) -> IndexParams { + unsafe { + (*self.0).kmeans_trainset_fraction = kmeans_trainset_fraction; + } + self + } + + /// After training the coarse and fine quantizers, we will populate + /// the index with the dataset if add_data_on_build == true, otherwise + /// the index is left empty, and the extend method can be used + /// to add new vectors to the index. + pub fn set_add_data_on_build(self, add_data_on_build: bool) -> IndexParams { + unsafe { + (*self.0).add_data_on_build = add_data_on_build; + } + self + } +} + +impl fmt::Debug for IndexParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // custom debug trait here, default value will show the pointer address + // for the inner params object which isn't that useful. + write!(f, "IndexParams({:?})", unsafe { *self.0 }) + } +} + +impl Drop for IndexParams { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsIvfFlatIndexParamsDestroy(self.0) }) { + write!( + stderr(), + "failed to call cuvsIvfFlatIndexParamsDestroy {:?}", + e + ) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_index_params() { + let params = IndexParams::new() + .unwrap() + .set_n_lists(128) + .set_add_data_on_build(false); + + unsafe { + assert_eq!((*params.0).n_lists, 128); + assert_eq!((*params.0).add_data_on_build, false); + } + } +} diff --git a/rust/cuvs/src/ivf_flat/mod.rs b/rust/cuvs/src/ivf_flat/mod.rs new file mode 100644 index 0000000000..32aa707845 --- /dev/null +++ b/rust/cuvs/src/ivf_flat/mod.rs @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! The IVF-Flat method is an ANN algorithm. It uses an inverted file index (IVF) with +//! unmodified (that is, flat) vectors. This algorithm provides simple knobs to reduce +//! the overall search space and to trade-off accuracy for speed. +//! +//! Example: +//! ``` +//! +//! use cuvs::ivf_flat::{Index, IndexParams, SearchParams}; +//! use cuvs::{ManagedTensor, Resources, Result}; +//! +//! use ndarray::s; +//! use ndarray_rand::rand_distr::Uniform; +//! use ndarray_rand::RandomExt; +//! +//! fn ivf_flat_example() -> Result<()> { +//! let res = Resources::new()?; +//! +//! // Create a new random dataset to index +//! let n_datapoints = 65536; +//! let n_features = 512; +//! let dataset = +//! ndarray::Array::::random((n_datapoints, n_features), Uniform::new(0., 1.0)); +//! +//! // build the ivf-flat index +//! let build_params = IndexParams::new()?; +//! let index = Index::build(&res, &build_params, &dataset)?; +//! println!( +//! "Indexed {}x{} datapoints into ivf-flat index", +//! n_datapoints, n_features +//! ); +//! +//! // use the first 4 points from the dataset as queries : will test that we get them back +//! // as their own nearest neighbor +//! let n_queries = 4; +//! let queries = dataset.slice(s![0..n_queries, ..]); +//! +//! let k = 10; +//! +//! // Ivf-Flat search API requires queries and outputs to be on device memory +//! // copy query data over, and allocate new device memory for the distances/ neighbors +//! // outputs +//! let queries = ManagedTensor::from(&queries).to_device(&res)?; +//! let mut neighbors_host = ndarray::Array::::zeros((n_queries, k)); +//! let neighbors = ManagedTensor::from(&neighbors_host).to_device(&res)?; +//! +//! let mut distances_host = ndarray::Array::::zeros((n_queries, k)); +//! let distances = ManagedTensor::from(&distances_host).to_device(&res)?; +//! +//! let search_params = SearchParams::new()?; +//! +//! index.search(&res, &search_params, &queries, &neighbors, &distances)?; +//! +//! // Copy back to host memory +//! distances.to_host(&res, &mut distances_host)?; +//! neighbors.to_host(&res, &mut neighbors_host)?; +//! +//! // nearest neighbors should be themselves, since queries are from the +//! // dataset +//! println!("Neighbors {:?}", neighbors_host); +//! println!("Distances {:?}", distances_host); +//! Ok(()) +//! } +//! ``` + +mod index; +mod index_params; +mod search_params; + +pub use index::Index; +pub use index_params::IndexParams; +pub use search_params::SearchParams; diff --git a/rust/cuvs/src/ivf_flat/search_params.rs b/rust/cuvs/src/ivf_flat/search_params.rs new file mode 100644 index 0000000000..9da2b22e73 --- /dev/null +++ b/rust/cuvs/src/ivf_flat/search_params.rs @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{check_cuvs, Result}; +use std::fmt; +use std::io::{stderr, Write}; + +/// Supplemental parameters to search IvfFlat index +pub struct SearchParams(pub ffi::cuvsIvfFlatSearchParams_t); + +impl SearchParams { + /// Returns a new SearchParams object + pub fn new() -> Result { + unsafe { + let mut params = std::mem::MaybeUninit::::uninit(); + check_cuvs(ffi::cuvsIvfFlatSearchParamsCreate(params.as_mut_ptr()))?; + Ok(SearchParams(params.assume_init())) + } + } + + /// Supplemental parameters to search IVF-Flat index + pub fn set_n_probes(self, n_probes: u32) -> SearchParams { + unsafe { + (*self.0).n_probes = n_probes; + } + self + } +} + +impl fmt::Debug for SearchParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // custom debug trait here, default value will show the pointer address + // for the inner params object which isn't that useful. + write!(f, "SearchParams {{ params: {:?} }}", unsafe { *self.0 }) + } +} + +impl Drop for SearchParams { + fn drop(&mut self) { + if let Err(e) = check_cuvs(unsafe { ffi::cuvsIvfFlatSearchParamsDestroy(self.0) }) { + write!( + stderr(), + "failed to call cuvsIvfFlatSearchParamsDestroy {:?}", + e + ) + .expect("failed to write to stderr"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search_params() { + let params = SearchParams::new().unwrap().set_n_probes(128); + + unsafe { + assert_eq!((*params.0).n_probes, 128); + } + } +} diff --git a/rust/cuvs/src/lib.rs b/rust/cuvs/src/lib.rs index 933b5a3828..6ed450c03e 100644 --- a/rust/cuvs/src/lib.rs +++ b/rust/cuvs/src/lib.rs @@ -21,6 +21,7 @@ pub mod brute_force; pub mod cagra; pub mod distance_type; +pub mod ivf_flat; mod dlpack; mod error; pub mod ivf_pq; From d44aa39f9b75dee3d3f5fdc038188333c1d177ac Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Thu, 2 May 2024 09:50:51 -1000 Subject: [PATCH 046/623] Enable Warnings as errors in Python tests (#102) Part of https://github.com/rapidsai/build-planning/issues/26, warnings in Python tests will now be treated as errors Authors: - Matthew Roeschke (https://github.com/mroeschke) Approvers: - Dante Gama Dessavre (https://github.com/dantegd) URL: https://github.com/rapidsai/cuvs/pull/102 --- python/cuvs/pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/cuvs/pyproject.toml b/python/cuvs/pyproject.toml index d3eae51ea5..c100511d58 100644 --- a/python/cuvs/pyproject.toml +++ b/python/cuvs/pyproject.toml @@ -120,3 +120,8 @@ wheel.packages = ["cuvs"] provider = "scikit_build_core.metadata.regex" input = "cuvs/VERSION" regex = "(?P.*)" + +[tool.pytest.ini_options] +filterwarnings = [ + "error", +] From 07791dfc23b46a211aac9df525c50885db372f9c Mon Sep 17 00:00:00 2001 From: Philip Hyunsu Cho Date: Mon, 6 May 2024 10:40:17 -0700 Subject: [PATCH 047/623] Migrate to `{{ stdlib("c") }}` (#93) The `sysroot*` syntax is getting phased out (conda-forge/conda-forge.github.io#2102). The recommendation is to move to `{{ stdlib("c") }}`. Ref https://github.com/rapidsai/build-planning/issues/39 Authors: - Philip Hyunsu Cho (https://github.com/hcho3) Approvers: - Bradley Dice (https://github.com/bdice) - https://github.com/jakirkham - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cuvs/pull/93 --- conda/recipes/cuvs/conda_build_config.yaml | 5 ++++- conda/recipes/cuvs/meta.yaml | 2 +- conda/recipes/libcuvs/conda_build_config.yaml | 5 ++++- conda/recipes/libcuvs/meta.yaml | 8 ++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/conda/recipes/cuvs/conda_build_config.yaml b/conda/recipes/cuvs/conda_build_config.yaml index e28b98da7f..e3ca633eb9 100644 --- a/conda/recipes/cuvs/conda_build_config.yaml +++ b/conda/recipes/cuvs/conda_build_config.yaml @@ -10,7 +10,10 @@ cuda_compiler: cuda11_compiler: - nvcc -sysroot_version: +c_stdlib: + - sysroot + +c_stdlib_version: - "2.17" cmake_version: diff --git a/conda/recipes/cuvs/meta.yaml b/conda/recipes/cuvs/meta.yaml index b2a8074007..349923bf0e 100644 --- a/conda/recipes/cuvs/meta.yaml +++ b/conda/recipes/cuvs/meta.yaml @@ -39,7 +39,7 @@ requirements: - cuda-version ={{ cuda_version }} - cmake {{ cmake_version }} - ninja - - sysroot_{{ target_platform }} {{ sysroot_version }} + - {{ stdlib("c") }} host: {% if cuda_major == "11" %} - cuda-python >=11.7.1,<12.0a0 diff --git a/conda/recipes/libcuvs/conda_build_config.yaml b/conda/recipes/libcuvs/conda_build_config.yaml index c833f68ba7..8141af1543 100644 --- a/conda/recipes/libcuvs/conda_build_config.yaml +++ b/conda/recipes/libcuvs/conda_build_config.yaml @@ -10,7 +10,10 @@ cuda_compiler: cuda11_compiler: - nvcc -sysroot_version: +c_stdlib: + - sysroot + +c_stdlib_version: - "2.17" cmake_version: diff --git a/conda/recipes/libcuvs/meta.yaml b/conda/recipes/libcuvs/meta.yaml index 7a09015ccc..d1e8506c87 100644 --- a/conda/recipes/libcuvs/meta.yaml +++ b/conda/recipes/libcuvs/meta.yaml @@ -61,7 +61,7 @@ outputs: - cuda-version ={{ cuda_version }} - cmake {{ cmake_version }} - ninja - - sysroot_{{ target_platform }} {{ sysroot_version }} + - {{ stdlib("c") }} host: - libraft ={{ minor_version }} - cuda-version ={{ cuda_version }} @@ -127,7 +127,7 @@ outputs: - cuda-version ={{ cuda_version }} - cmake {{ cmake_version }} - ninja - - sysroot_{{ target_platform }} {{ sysroot_version }} + - {{ stdlib("c") }} host: - libraft ={{ minor_version }} - cuda-version ={{ cuda_version }} @@ -193,7 +193,7 @@ outputs: - cuda-version ={{ cuda_version }} - cmake {{ cmake_version }} - ninja - - sysroot_{{ target_platform }} {{ sysroot_version }} + - {{ stdlib("c") }} host: - libraft ={{ minor_version }} - {{ pin_subpackage('libcuvs', exact=True) }} @@ -260,7 +260,7 @@ outputs: - cuda-version ={{ cuda_version }} - cmake {{ cmake_version }} - ninja - - sysroot_{{ target_platform }} {{ sysroot_version }} + - {{ stdlib("c") }} host: - libraft ={{ minor_version }} - {{ pin_subpackage('libcuvs', exact=True) }} From 22f107cea465c2f6a59913c38e51dea988f430f0 Mon Sep 17 00:00:00 2001 From: Micka Date: Sat, 11 May 2024 00:20:41 +0200 Subject: [PATCH 048/623] Migrate IVF-PQ from RAFT to cuVS (#86) List of changes I made during the migration: - The unit tests are testing IVFPQ codepacker and build functions. So these functions had to be exposed (through `cuvs/neighbors/details`) - The bitset filter is now located under `cuvs/neighbors/bitset_filter.cuh" - search_with_filter is added to the public API, with bitset_filter - Bitset is exposed in public API in a `.hpp` header, for inclusion in CUDA-free code. `bitset::test()` function has been temporarily disabled due to the switch from `.cuh` to `.hpp`. Authors: - Micka (https://github.com/lowener) - Corey J. Nolet (https://github.com/cjnolet) Approvers: - Corey J. Nolet (https://github.com/cjnolet) URL: https://github.com/rapidsai/cuvs/pull/86 --- cpp/CMakeLists.txt | 209 +- cpp/include/cuvs/core/bitset.hpp | 30 + cpp/include/cuvs/neighbors/ann_types.hpp | 40 +- cpp/include/cuvs/neighbors/cagra.hpp | 213 +- cpp/include/cuvs/neighbors/dataset.hpp | 298 +++ cpp/include/cuvs/neighbors/ivf_list.hpp | 123 ++ cpp/include/cuvs/neighbors/ivf_pq.hpp | 279 ++- cpp/include/cuvs/neighbors/ivf_pq_helpers.hpp | 274 +++ cpp/include/cuvs/neighbors/nn_descent.hpp | 342 +++ cpp/include/cuvs/neighbors/sample_filter.hpp | 171 ++ cpp/src/core/nvtx.hpp | 24 + cpp/src/neighbors/cagra.cuh | 399 ++++ ...ra_build_int8.cpp => cagra_build_float.cu} | 70 +- ...ra_build_uint8.cpp => cagra_build_int8.cu} | 70 +- ...a_build_float.cpp => cagra_build_uint8.cu} | 72 +- cpp/src/neighbors/cagra_c.cpp | 4 +- .../{cagra_optimize.cpp => cagra_optimize.cu} | 18 +- ...search_float.cpp => cagra_search_float.cu} | 21 +- ...a_search_int8.cpp => cagra_search_int8.cu} | 22 +- ...search_uint8.cpp => cagra_search_uint8.cu} | 21 +- cpp/src/neighbors/cagra_serialize.cuh | 230 ++ ...lize_int8.cpp => cagra_serialize_float.cu} | 47 +- ...lize_uint8.cpp => cagra_serialize_int8.cu} | 47 +- ...ize_float.cpp => cagra_serialize_uint8.cu} | 47 +- cpp/src/neighbors/detail/ann_utils.cuh | 577 +++++ cpp/src/neighbors/detail/cagra/bitonic.hpp | 228 ++ .../neighbors/detail/cagra/cagra_build.cuh | 384 ++++ .../neighbors/detail/cagra/cagra_search.cuh | 338 +++ .../detail/cagra/cagra_serialize.cuh | 279 +++ .../detail/cagra/compute_distance.hpp | 323 +++ .../detail/cagra/compute_distance_vpq.cuh | 231 ++ .../neighbors/detail/cagra/device_common.hpp | 60 + cpp/src/neighbors/detail/cagra/factory.cuh | 95 + cpp/src/neighbors/detail/cagra/graph_core.cuh | 582 +++++ cpp/src/neighbors/detail/cagra/hashmap.hpp | 82 + .../cagra/q_search_multi_cta_00_generate.py | 83 + ...float_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ...float_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...a_float_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...a_float_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ..._float_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ..._float_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ..._float_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ..._float_uint32_dim512_t32_8pq_4subd_half.cu | 36 + ...float_uint64_dim1024_t32_8pq_2subd_half.cu | 36 + ...float_uint64_dim1024_t32_8pq_4subd_half.cu | 36 + ...a_float_uint64_dim128_t8_8pq_2subd_half.cu | 36 + ...a_float_uint64_dim128_t8_8pq_4subd_half.cu | 36 + ..._float_uint64_dim256_t16_8pq_2subd_half.cu | 36 + ..._float_uint64_dim256_t16_8pq_4subd_half.cu | 36 + ..._float_uint64_dim512_t32_8pq_2subd_half.cu | 36 + ..._float_uint64_dim512_t32_8pq_4subd_half.cu | 36 + ..._half_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ..._half_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...ta_half_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...ta_half_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ...a_half_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ...a_half_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ...a_half_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ...a_half_uint32_dim512_t32_8pq_4subd_half.cu | 36 + ..._half_uint64_dim1024_t32_8pq_2subd_half.cu | 36 + ..._half_uint64_dim1024_t32_8pq_4subd_half.cu | 36 + ...ta_half_uint64_dim128_t8_8pq_2subd_half.cu | 36 + ...ta_half_uint64_dim128_t8_8pq_4subd_half.cu | 36 + ...a_half_uint64_dim256_t16_8pq_2subd_half.cu | 36 + ...a_half_uint64_dim256_t16_8pq_4subd_half.cu | 36 + ...a_half_uint64_dim512_t32_8pq_2subd_half.cu | 36 + ...a_half_uint64_dim512_t32_8pq_4subd_half.cu | 36 + ..._int8_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ..._int8_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...ta_int8_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...ta_int8_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ...a_int8_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ...a_int8_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ...a_int8_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ...a_int8_uint32_dim512_t32_8pq_4subd_half.cu | 36 + ...uint8_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ...uint8_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...a_uint8_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...a_uint8_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ..._uint8_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ..._uint8_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ..._uint8_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ..._uint8_uint32_dim512_t32_8pq_4subd_half.cu | 36 + .../cagra/q_search_single_cta_00_generate.py | 88 + ...float_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ...float_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...a_float_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...a_float_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ..._float_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ..._float_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ..._float_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ..._float_uint32_dim512_t32_8pq_4subd_half.cu | 36 + ...float_uint64_dim1024_t32_8pq_2subd_half.cu | 36 + ...float_uint64_dim1024_t32_8pq_4subd_half.cu | 36 + ...a_float_uint64_dim128_t8_8pq_2subd_half.cu | 36 + ...a_float_uint64_dim128_t8_8pq_4subd_half.cu | 36 + ..._float_uint64_dim256_t16_8pq_2subd_half.cu | 36 + ..._float_uint64_dim256_t16_8pq_4subd_half.cu | 36 + ..._float_uint64_dim512_t32_8pq_2subd_half.cu | 36 + ..._float_uint64_dim512_t32_8pq_4subd_half.cu | 36 + ..._half_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ..._half_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...ta_half_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...ta_half_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ...a_half_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ...a_half_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ...a_half_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ...a_half_uint32_dim512_t32_8pq_4subd_half.cu | 36 + ..._half_uint64_dim1024_t32_8pq_2subd_half.cu | 36 + ..._half_uint64_dim1024_t32_8pq_4subd_half.cu | 36 + ...ta_half_uint64_dim128_t8_8pq_2subd_half.cu | 36 + ...ta_half_uint64_dim128_t8_8pq_4subd_half.cu | 36 + ...a_half_uint64_dim256_t16_8pq_2subd_half.cu | 36 + ...a_half_uint64_dim256_t16_8pq_4subd_half.cu | 36 + ...a_half_uint64_dim512_t32_8pq_2subd_half.cu | 36 + ...a_half_uint64_dim512_t32_8pq_4subd_half.cu | 36 + ..._int8_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ..._int8_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...ta_int8_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...ta_int8_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ...a_int8_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ...a_int8_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ...a_int8_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ...a_int8_uint32_dim512_t32_8pq_4subd_half.cu | 36 + ...uint8_uint32_dim1024_t32_8pq_2subd_half.cu | 36 + ...uint8_uint32_dim1024_t32_8pq_4subd_half.cu | 36 + ...a_uint8_uint32_dim128_t8_8pq_2subd_half.cu | 36 + ...a_uint8_uint32_dim128_t8_8pq_4subd_half.cu | 36 + ..._uint8_uint32_dim256_t16_8pq_2subd_half.cu | 36 + ..._uint8_uint32_dim256_t16_8pq_4subd_half.cu | 36 + ..._uint8_uint32_dim512_t32_8pq_2subd_half.cu | 36 + ..._uint8_uint32_dim512_t32_8pq_4subd_half.cu | 36 + .../detail/cagra/search_multi_cta.cuh | 273 +++ .../cagra/search_multi_cta_00_generate.py | 78 + ...arch_multi_cta_float_uint32_dim1024_t32.cu | 37 + ...search_multi_cta_float_uint32_dim128_t8.cu | 37 + ...earch_multi_cta_float_uint32_dim256_t16.cu | 37 + ...earch_multi_cta_float_uint32_dim512_t32.cu | 37 + ...arch_multi_cta_float_uint64_dim1024_t32.cu | 37 + ...search_multi_cta_float_uint64_dim128_t8.cu | 37 + ...earch_multi_cta_float_uint64_dim256_t16.cu | 37 + ...earch_multi_cta_float_uint64_dim512_t32.cu | 37 + ...earch_multi_cta_half_uint32_dim1024_t32.cu | 37 + .../search_multi_cta_half_uint32_dim128_t8.cu | 37 + ...search_multi_cta_half_uint32_dim256_t16.cu | 37 + ...search_multi_cta_half_uint32_dim512_t32.cu | 37 + ...earch_multi_cta_half_uint64_dim1024_t32.cu | 37 + .../search_multi_cta_half_uint64_dim128_t8.cu | 37 + ...search_multi_cta_half_uint64_dim256_t16.cu | 37 + ...search_multi_cta_half_uint64_dim512_t32.cu | 37 + .../detail/cagra/search_multi_cta_inst.cuh | 53 + ...earch_multi_cta_int8_uint32_dim1024_t32.cu | 37 + .../search_multi_cta_int8_uint32_dim128_t8.cu | 37 + ...search_multi_cta_int8_uint32_dim256_t16.cu | 37 + ...search_multi_cta_int8_uint32_dim512_t32.cu | 37 + .../cagra/search_multi_cta_kernel-ext.cuh | 419 ++++ .../cagra/search_multi_cta_kernel-inl.cuh | 522 +++++ .../detail/cagra/search_multi_cta_kernel.cuh | 24 + ...arch_multi_cta_uint8_uint32_dim1024_t32.cu | 37 + ...search_multi_cta_uint8_uint32_dim128_t8.cu | 37 + ...earch_multi_cta_uint8_uint32_dim256_t16.cu | 37 + ...earch_multi_cta_uint8_uint32_dim512_t32.cu | 37 + .../detail/cagra/search_multi_kernel.cuh | 1095 ++++++++++ .../neighbors/detail/cagra/search_plan.cuh | 349 +++ .../detail/cagra/search_single_cta.cuh | 258 +++ .../cagra/search_single_cta_00_generate.py | 82 + ...rch_single_cta_float_uint32_dim1024_t32.cu | 37 + ...earch_single_cta_float_uint32_dim128_t8.cu | 37 + ...arch_single_cta_float_uint32_dim256_t16.cu | 37 + ...arch_single_cta_float_uint32_dim512_t32.cu | 37 + ...rch_single_cta_float_uint64_dim1024_t32.cu | 37 + ...earch_single_cta_float_uint64_dim128_t8.cu | 37 + ...arch_single_cta_float_uint64_dim256_t16.cu | 37 + ...arch_single_cta_float_uint64_dim512_t32.cu | 37 + ...arch_single_cta_half_uint32_dim1024_t32.cu | 37 + ...search_single_cta_half_uint32_dim128_t8.cu | 37 + ...earch_single_cta_half_uint32_dim256_t16.cu | 37 + ...earch_single_cta_half_uint32_dim512_t32.cu | 37 + ...arch_single_cta_half_uint64_dim1024_t32.cu | 37 + ...search_single_cta_half_uint64_dim128_t8.cu | 37 + ...earch_single_cta_half_uint64_dim256_t16.cu | 37 + ...earch_single_cta_half_uint64_dim512_t32.cu | 37 + .../detail/cagra/search_single_cta_inst.cuh | 54 + ...arch_single_cta_int8_uint32_dim1024_t32.cu | 37 + ...search_single_cta_int8_uint32_dim128_t8.cu | 37 + ...earch_single_cta_int8_uint32_dim256_t16.cu | 37 + ...earch_single_cta_int8_uint32_dim512_t32.cu | 37 + .../cagra/search_single_cta_kernel-ext.cuh | 603 ++++++ .../cagra/search_single_cta_kernel-inl.cuh | 980 +++++++++ .../detail/cagra/search_single_cta_kernel.cuh | 24 + ...rch_single_cta_uint8_uint32_dim1024_t32.cu | 37 + ...earch_single_cta_uint8_uint32_dim128_t8.cu | 37 + ...arch_single_cta_uint8_uint32_dim256_t16.cu | 37 + ...arch_single_cta_uint8_uint32_dim512_t32.cu | 37 + .../neighbors/detail/cagra/topk_by_radix.cuh | 91 + .../detail/cagra/topk_for_cagra/topk.h | 59 + .../detail/cagra/topk_for_cagra/topk_core.cuh | 1040 +++++++++ cpp/src/neighbors/detail/cagra/utils.hpp | 297 +++ .../neighbors/detail/dataset_serialize.hpp | 197 ++ cpp/src/neighbors/detail/nn_descent.cuh | 1457 +++++++++++++ cpp/src/neighbors/detail/refine.cuh | 19 + cpp/src/neighbors/detail/refine_common.hpp | 57 + cpp/src/neighbors/detail/refine_device.cuh | 145 ++ cpp/src/neighbors/detail/refine_host-ext.hpp | 66 + cpp/src/neighbors/detail/refine_host-inl.hpp | 193 ++ cpp/src/neighbors/detail/refine_host.hpp | 19 + .../detail/refine_host_float_float.cpp | 30 + .../detail/refine_host_half_float.cpp | 31 + .../detail/refine_host_int8_t_float.cpp | 29 + .../detail/refine_host_uint8_t_float.cpp | 30 + cpp/src/neighbors/detail/vpq_dataset.cuh | 431 ++++ cpp/src/neighbors/ivf_common.cuh | 327 +++ cpp/src/neighbors/ivf_list.cuh | 191 ++ .../ivf_pq/{ => detail}/generate_ivf_pq.py | 100 +- .../generate_ivf_pq_compute_similarity.py | 116 + .../ivf_pq_build_extend_float_int64_t.cu | 68 + .../ivf_pq_build_extend_int8_t_int64_t.cu | 68 + .../ivf_pq_build_extend_uint8_t_int64_t.cu | 68 + .../ivf_pq_compute_similarity_float_float.cu | 74 + ...compute_similarity_float_float_bitset64.cu | 74 + ...f_pq_compute_similarity_float_fp8_false.cu | 74 + ...ute_similarity_float_fp8_false_bitset64.cu | 74 + ...vf_pq_compute_similarity_float_fp8_true.cu | 74 + ...pute_similarity_float_fp8_true_bitset64.cu | 74 + .../ivf_pq_compute_similarity_float_half.cu | 74 + ..._compute_similarity_float_half_bitset64.cu | 74 + ...vf_pq_compute_similarity_half_fp8_false.cu | 74 + ...pute_similarity_half_fp8_false_bitset64.cu | 74 + ...ivf_pq_compute_similarity_half_fp8_true.cu | 74 + ...mpute_similarity_half_fp8_true_bitset64.cu | 74 + .../ivf_pq_compute_similarity_half_half.cu | 74 + ...q_compute_similarity_half_half_bitset64.cu | 74 + .../ivf_pq_search_float_int64_t.cu} | 22 +- .../ivf_pq_search_int8_t_int64_t.cu} | 22 +- .../ivf_pq_search_uint8_t_int64_t.cu} | 22 +- ...ivf_pq_search_with_filter_float_int64_t.cu | 49 + ...vf_pq_search_with_filter_int8_t_int64_t.cu | 49 + ...f_pq_search_with_filter_uint8_t_int64_t.cu | 49 + cpp/src/neighbors/ivf_pq/ivf_pq_build.cuh | 1884 +++++++++++++++++ .../neighbors/ivf_pq/ivf_pq_build_common.cu | 167 ++ .../ivf_pq/ivf_pq_build_float_int64_t.cpp | 52 - .../ivf_pq/ivf_pq_build_int8_t_int64_t.cpp | 52 - .../ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp | 52 - .../neighbors/ivf_pq/ivf_pq_codepacking.cuh | 219 ++ .../ivf_pq/ivf_pq_compute_similarity.cuh | 249 +++ .../ivf_pq/ivf_pq_compute_similarity_impl.cuh | 942 +++++++++ .../neighbors/ivf_pq/ivf_pq_deserialize.cu | 30 + .../ivf_pq/ivf_pq_extend_float_int64_t.cpp | 55 - .../ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp | 55 - .../ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp | 55 - cpp/src/neighbors/ivf_pq/ivf_pq_fp_8bit.cuh | 114 + cpp/src/neighbors/ivf_pq/ivf_pq_list.cuh | 55 + cpp/src/neighbors/ivf_pq/ivf_pq_search.cuh | 797 +++++++ cpp/src/neighbors/ivf_pq/ivf_pq_serialize.cu | 29 + cpp/src/neighbors/ivf_pq/ivf_pq_serialize.cuh | 193 ++ cpp/src/neighbors/ivf_pq_index.cpp | 182 +- cpp/src/neighbors/ivf_pq_serialize.cpp | 40 - cpp/src/neighbors/nn_descent.cuh | 182 ++ cpp/src/neighbors/nn_descent_float.cu | 43 + cpp/src/neighbors/nn_descent_int8.cu | 43 + cpp/src/neighbors/nn_descent_uint8.cu | 43 + cpp/src/neighbors/refine-ext.cuh | 85 + cpp/src/neighbors/refine-inl.cuh | 104 + cpp/src/neighbors/refine.cuh | 24 + cpp/src/neighbors/refine_float_float.cu | 54 + cpp/src/neighbors/refine_half_float.cu | 50 + cpp/src/neighbors/refine_int8_t_float.cu | 50 + cpp/src/neighbors/refine_uint8_t_float.cu | 50 + cpp/src/neighbors/sample_filter.cu | 26 + cpp/src/neighbors/sample_filter.cuh | 110 + cpp/src/neighbors/vpq_dataset.cuh | 52 + cpp/test/CMakeLists.txt | 13 + cpp/test/neighbors/ann_cagra.cuh | 24 +- cpp/test/neighbors/ann_ivf_flat.cuh | 2 +- cpp/test/neighbors/ann_ivf_pq.cuh | 162 +- .../ann_ivf_pq/test_float_int64_t.cu | 6 +- .../ann_ivf_pq/test_int8_t_int64_t.cu | 5 +- .../ann_ivf_pq/test_uint8_t_int64_t.cu | 5 +- cpp/test/neighbors/ann_nn_descent.cuh | 157 ++ .../ann_nn_descent/test_float_uint32_t.cu | 28 + .../ann_nn_descent/test_int8_t_uint32_t.cu | 28 + .../ann_nn_descent/test_uint8_t_uint32_t.cu | 28 + cpp/test/neighbors/ann_utils.cuh | 55 +- cpp/test/neighbors/ivf_pq_helpers.cuh | 428 ---- 285 files changed, 28539 insertions(+), 1385 deletions(-) create mode 100644 cpp/include/cuvs/core/bitset.hpp create mode 100644 cpp/include/cuvs/neighbors/dataset.hpp create mode 100644 cpp/include/cuvs/neighbors/ivf_list.hpp create mode 100644 cpp/include/cuvs/neighbors/ivf_pq_helpers.hpp create mode 100644 cpp/include/cuvs/neighbors/nn_descent.hpp create mode 100644 cpp/include/cuvs/neighbors/sample_filter.hpp create mode 100644 cpp/src/core/nvtx.hpp create mode 100644 cpp/src/neighbors/cagra.cuh rename cpp/src/neighbors/{cagra_build_int8.cpp => cagra_build_float.cu} (57%) rename cpp/src/neighbors/{cagra_build_uint8.cpp => cagra_build_int8.cu} (57%) rename cpp/src/neighbors/{cagra_build_float.cpp => cagra_build_uint8.cu} (56%) rename cpp/src/neighbors/{cagra_optimize.cpp => cagra_optimize.cu} (52%) rename cpp/src/neighbors/{cagra_search_float.cpp => cagra_search_float.cu} (75%) rename cpp/src/neighbors/{cagra_search_int8.cpp => cagra_search_int8.cu} (75%) rename cpp/src/neighbors/{cagra_search_uint8.cpp => cagra_search_uint8.cu} (75%) create mode 100644 cpp/src/neighbors/cagra_serialize.cuh rename cpp/src/neighbors/{cagra_serialize_int8.cpp => cagra_serialize_float.cu} (56%) rename cpp/src/neighbors/{cagra_serialize_uint8.cpp => cagra_serialize_int8.cu} (56%) rename cpp/src/neighbors/{cagra_serialize_float.cpp => cagra_serialize_uint8.cu} (56%) create mode 100644 cpp/src/neighbors/detail/ann_utils.cuh create mode 100644 cpp/src/neighbors/detail/cagra/bitonic.hpp create mode 100644 cpp/src/neighbors/detail/cagra/cagra_build.cuh create mode 100644 cpp/src/neighbors/detail/cagra/cagra_search.cuh create mode 100644 cpp/src/neighbors/detail/cagra/cagra_serialize.cuh create mode 100644 cpp/src/neighbors/detail/cagra/compute_distance.hpp create mode 100644 cpp/src/neighbors/detail/cagra/compute_distance_vpq.cuh create mode 100644 cpp/src/neighbors/detail/cagra/device_common.hpp create mode 100644 cpp/src/neighbors/detail/cagra/factory.cuh create mode 100644 cpp/src/neighbors/detail/cagra/graph_core.cuh create mode 100644 cpp/src/neighbors/detail/cagra/hashmap.hpp create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_00_generate.py create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_00_generate.py create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim1024_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim1024_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim128_t8_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim128_t8_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim256_t16_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim256_t16_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim512_t32_8pq_2subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim512_t32_8pq_4subd_half.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_00_generate.py create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_inst.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_kernel-ext.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_kernel-inl.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_kernel.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_multi_kernel.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_plan.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_00_generate.py create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint64_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint64_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint64_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_float_uint64_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint64_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint64_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint64_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_half_uint64_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_inst.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_kernel-ext.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_kernel-inl.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_kernel.cuh create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim1024_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim128_t8.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim256_t16.cu create mode 100644 cpp/src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim512_t32.cu create mode 100644 cpp/src/neighbors/detail/cagra/topk_by_radix.cuh create mode 100644 cpp/src/neighbors/detail/cagra/topk_for_cagra/topk.h create mode 100644 cpp/src/neighbors/detail/cagra/topk_for_cagra/topk_core.cuh create mode 100644 cpp/src/neighbors/detail/cagra/utils.hpp create mode 100644 cpp/src/neighbors/detail/dataset_serialize.hpp create mode 100644 cpp/src/neighbors/detail/nn_descent.cuh create mode 100644 cpp/src/neighbors/detail/refine.cuh create mode 100644 cpp/src/neighbors/detail/refine_common.hpp create mode 100644 cpp/src/neighbors/detail/refine_device.cuh create mode 100644 cpp/src/neighbors/detail/refine_host-ext.hpp create mode 100644 cpp/src/neighbors/detail/refine_host-inl.hpp create mode 100644 cpp/src/neighbors/detail/refine_host.hpp create mode 100644 cpp/src/neighbors/detail/refine_host_float_float.cpp create mode 100644 cpp/src/neighbors/detail/refine_host_half_float.cpp create mode 100644 cpp/src/neighbors/detail/refine_host_int8_t_float.cpp create mode 100644 cpp/src/neighbors/detail/refine_host_uint8_t_float.cpp create mode 100644 cpp/src/neighbors/detail/vpq_dataset.cuh create mode 100644 cpp/src/neighbors/ivf_common.cuh create mode 100644 cpp/src/neighbors/ivf_list.cuh rename cpp/src/neighbors/ivf_pq/{ => detail}/generate_ivf_pq.py (62%) create mode 100644 cpp/src/neighbors/ivf_pq/detail/generate_ivf_pq_compute_similarity.py create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_build_extend_float_int64_t.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_build_extend_int8_t_int64_t.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_build_extend_uint8_t_int64_t.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_float.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_float_bitset64.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_false.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_false_bitset64.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_true.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_true_bitset64.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_half.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_half_bitset64.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_false.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_false_bitset64.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_true.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_true_bitset64.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_half.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_half_bitset64.cu rename cpp/src/neighbors/ivf_pq/{ivf_pq_search_float_int64_t.cpp => detail/ivf_pq_search_float_int64_t.cu} (77%) rename cpp/src/neighbors/ivf_pq/{ivf_pq_search_int8_t_int64_t.cpp => detail/ivf_pq_search_int8_t_int64_t.cu} (77%) rename cpp/src/neighbors/ivf_pq/{ivf_pq_search_uint8_t_int64_t.cpp => detail/ivf_pq_search_uint8_t_int64_t.cu} (77%) create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_search_with_filter_float_int64_t.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_search_with_filter_int8_t_int64_t.cu create mode 100644 cpp/src/neighbors/ivf_pq/detail/ivf_pq_search_with_filter_uint8_t_int64_t.cu create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build.cuh create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build_common.cu delete mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build_float_int64_t.cpp delete mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build_int8_t_int64_t.cpp delete mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_codepacking.cuh create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_compute_similarity.cuh create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_compute_similarity_impl.cuh create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_deserialize.cu delete mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_extend_float_int64_t.cpp delete mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp delete mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_fp_8bit.cuh create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_list.cuh create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_search.cuh create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_serialize.cu create mode 100644 cpp/src/neighbors/ivf_pq/ivf_pq_serialize.cuh delete mode 100644 cpp/src/neighbors/ivf_pq_serialize.cpp create mode 100644 cpp/src/neighbors/nn_descent.cuh create mode 100644 cpp/src/neighbors/nn_descent_float.cu create mode 100644 cpp/src/neighbors/nn_descent_int8.cu create mode 100644 cpp/src/neighbors/nn_descent_uint8.cu create mode 100644 cpp/src/neighbors/refine-ext.cuh create mode 100644 cpp/src/neighbors/refine-inl.cuh create mode 100644 cpp/src/neighbors/refine.cuh create mode 100644 cpp/src/neighbors/refine_float_float.cu create mode 100644 cpp/src/neighbors/refine_half_float.cu create mode 100644 cpp/src/neighbors/refine_int8_t_float.cu create mode 100644 cpp/src/neighbors/refine_uint8_t_float.cu create mode 100644 cpp/src/neighbors/sample_filter.cu create mode 100644 cpp/src/neighbors/sample_filter.cuh create mode 100644 cpp/src/neighbors/vpq_dataset.cuh create mode 100644 cpp/test/neighbors/ann_nn_descent.cuh create mode 100644 cpp/test/neighbors/ann_nn_descent/test_float_uint32_t.cu create mode 100644 cpp/test/neighbors/ann_nn_descent/test_int8_t_uint32_t.cu create mode 100644 cpp/test/neighbors/ann_nn_descent/test_uint8_t_uint32_t.cu delete mode 100644 cpp/test/neighbors/ivf_pq_helpers.cuh diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 8556e2941e..2b1fe8aa09 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -34,6 +34,7 @@ project( VERSION "${RAPIDS_VERSION}" LANGUAGES ${lang_list} ) +set(CMAKE_INSTALL_MESSAGE LAZY) # Write the version header rapids_cmake_write_version_file(include/cuvs/version_config.hpp) @@ -219,16 +220,164 @@ add_library( src/distance/pairwise_distance.cu src/neighbors/brute_force_index.cu src/neighbors/brute_force.cu - src/neighbors/cagra_build_float.cpp - src/neighbors/cagra_build_int8.cpp - src/neighbors/cagra_build_uint8.cpp - src/neighbors/cagra_optimize.cpp - src/neighbors/cagra_search_float.cpp - src/neighbors/cagra_search_int8.cpp - src/neighbors/cagra_search_uint8.cpp - src/neighbors/cagra_serialize_float.cpp - src/neighbors/cagra_serialize_int8.cpp - src/neighbors/cagra_serialize_uint8.cpp + src/neighbors/cagra_build_float.cu + src/neighbors/cagra_build_int8.cu + src/neighbors/cagra_build_uint8.cu + src/neighbors/cagra_optimize.cu + src/neighbors/cagra_search_float.cu + src/neighbors/cagra_search_int8.cu + src/neighbors/cagra_search_uint8.cu + src/neighbors/cagra_serialize_float.cu + src/neighbors/cagra_serialize_int8.cu + src/neighbors/cagra_serialize_uint8.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_int8_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_uint8_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_float_uint64_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_multi_cta_half_uint64_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_int8_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_uint8_uint32_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_float_uint64_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim128_t8_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim128_t8_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim256_t16_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim256_t16_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim512_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim512_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim1024_t32_8pq_2subd_half.cu + src/neighbors/detail/cagra/q_search_single_cta_half_uint64_dim1024_t32_8pq_4subd_half.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_multi_cta_int8_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_multi_cta_uint8_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim128_t8.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim256_t16.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim512_t32.cu + src/neighbors/detail/cagra/search_multi_cta_float_uint64_dim1024_t32.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim128_t8.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim256_t16.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim512_t32.cu + src/neighbors/detail/cagra/search_multi_cta_half_uint64_dim1024_t32.cu + src/neighbors/detail/cagra/search_single_cta_float_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_single_cta_float_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_single_cta_float_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_single_cta_float_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_single_cta_half_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_single_cta_half_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_single_cta_half_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_single_cta_half_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_single_cta_int8_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim128_t8.cu + src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim256_t16.cu + src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim512_t32.cu + src/neighbors/detail/cagra/search_single_cta_uint8_uint32_dim1024_t32.cu + src/neighbors/detail/cagra/search_single_cta_float_uint64_dim128_t8.cu + src/neighbors/detail/cagra/search_single_cta_float_uint64_dim256_t16.cu + src/neighbors/detail/cagra/search_single_cta_float_uint64_dim512_t32.cu + src/neighbors/detail/cagra/search_single_cta_float_uint64_dim1024_t32.cu + src/neighbors/detail/cagra/search_single_cta_half_uint64_dim128_t8.cu + src/neighbors/detail/cagra/search_single_cta_half_uint64_dim256_t16.cu + src/neighbors/detail/cagra/search_single_cta_half_uint64_dim512_t32.cu + src/neighbors/detail/cagra/search_single_cta_half_uint64_dim1024_t32.cu + src/neighbors/detail/refine_host_float_float.cpp + src/neighbors/detail/refine_host_half_float.cpp + src/neighbors/detail/refine_host_int8_t_float.cpp + src/neighbors/detail/refine_host_uint8_t_float.cpp src/neighbors/ivf_flat_index.cpp src/neighbors/ivf_flat/ivf_flat_build_float_int64_t.cpp src/neighbors/ivf_flat/ivf_flat_build_int8_t_int64_t.cpp @@ -243,16 +392,36 @@ add_library( src/neighbors/ivf_flat/ivf_flat_serialize_int8_t_int64_t.cpp src/neighbors/ivf_flat/ivf_flat_serialize_uint8_t_int64_t.cpp src/neighbors/ivf_pq_index.cpp - src/neighbors/ivf_pq/ivf_pq_build_float_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_build_int8_t_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_build_uint8_t_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_extend_float_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_extend_int8_t_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_extend_uint8_t_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_search_float_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_search_int8_t_int64_t.cpp - src/neighbors/ivf_pq/ivf_pq_search_uint8_t_int64_t.cpp - src/neighbors/ivf_pq_serialize.cpp + src/neighbors/ivf_pq/ivf_pq_build_common.cu + src/neighbors/ivf_pq/ivf_pq_serialize.cu + src/neighbors/ivf_pq/ivf_pq_deserialize.cu + src/neighbors/ivf_pq/detail/ivf_pq_build_extend_float_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_build_extend_int8_t_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_build_extend_uint8_t_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_false.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_true.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_half.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_half.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_float.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_false.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_true.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_false_bitset64.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_fp8_true_bitset64.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_half_half_bitset64.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_half_bitset64.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_float_bitset64.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_false_bitset64.cu + src/neighbors/ivf_pq/detail/ivf_pq_compute_similarity_float_fp8_true_bitset64.cu + src/neighbors/ivf_pq/detail/ivf_pq_search_float_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_search_int8_t_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_search_uint8_t_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_search_with_filter_float_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_search_with_filter_int8_t_int64_t.cu + src/neighbors/ivf_pq/detail/ivf_pq_search_with_filter_uint8_t_int64_t.cu + src/neighbors/nn_descent_float.cu + src/neighbors/nn_descent_int8.cu + src/neighbors/nn_descent_uint8.cu + src/neighbors/sample_filter.cu ) target_compile_options( diff --git a/cpp/include/cuvs/core/bitset.hpp b/cpp/include/cuvs/core/bitset.hpp new file mode 100644 index 0000000000..99942e21c4 --- /dev/null +++ b/cpp/include/cuvs/core/bitset.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace cuvs::core { +/* To use bitset functions containing CUDA code, include */ + +template +using bitset_view = raft::core::bitset_view; + +template +using bitset = raft::core::bitset; + +} // end namespace cuvs::core diff --git a/cpp/include/cuvs/neighbors/ann_types.hpp b/cpp/include/cuvs/neighbors/ann_types.hpp index 36b545ebd0..e9b7bd903f 100644 --- a/cpp/include/cuvs/neighbors/ann_types.hpp +++ b/cpp/include/cuvs/neighbors/ann_types.hpp @@ -16,10 +16,48 @@ #pragma once +#include #include namespace cuvs::neighbors::ann { +/** Parameters for VPQ compression. */ +struct vpq_params { + /** + * The bit length of the vector element after compression by PQ. + * + * Possible values: [4, 5, 6, 7, 8]. + * + * Hint: the smaller the 'pq_bits', the smaller the index size and the better the search + * performance, but the lower the recall. + */ + uint32_t pq_bits = 8; + /** + * The dimensionality of the vector after compression by PQ. + * When zero, an optimal value is selected using a heuristic. + * + * TODO: at the moment `dim` must be a multiple `pq_dim`. + */ + uint32_t pq_dim = 0; + /** + * Vector Quantization (VQ) codebook size - number of "coarse cluster centers". + * When zero, an optimal value is selected using a heuristic. + */ + uint32_t vq_n_centers = 0; + /** The number of iterations searching for kmeans centers (both VQ & PQ phases). */ + uint32_t kmeans_n_iters = 25; + /** + * The fraction of data to use during iterative kmeans building (VQ phase). + * When zero, an optimal value is selected using a heuristic. + */ + double vq_kmeans_trainset_fraction = 0; + /** + * The fraction of data to use during iterative kmeans building (PQ phase). + * When zero, an optimal value is selected using a heuristic. + */ + double pq_kmeans_trainset_fraction = 0; +}; + /** * @defgroup ann_types Approximate Nearest Neighbors Types * @{ @@ -31,7 +69,7 @@ struct index {}; /** The base for KNN index parameters. */ struct index_params { /** Distance type. */ - cuvs::distance::DistanceType metric = distance::DistanceType::L2Expanded; + cuvs::distance::DistanceType metric = cuvs::distance::DistanceType::L2Expanded; /** The argument used by some distance metrics. */ float metric_arg = 2.0f; /** diff --git a/cpp/include/cuvs/neighbors/cagra.hpp b/cpp/include/cuvs/neighbors/cagra.hpp index d6f5fef496..e52c15937a 100644 --- a/cpp/include/cuvs/neighbors/cagra.hpp +++ b/cpp/include/cuvs/neighbors/cagra.hpp @@ -18,11 +18,14 @@ #include "ann_types.hpp" #include +#include #include #include +#include #include +#include #include -#include +#include #include #include @@ -44,54 +47,6 @@ enum class graph_build_algo { NN_DESCENT }; -/** Parameters for VPQ compression. */ -struct vpq_params { - /** - * The bit length of the vector element after compression by PQ. - * - * Possible values: [4, 5, 6, 7, 8]. - * - * Hint: the smaller the 'pq_bits', the smaller the index size and the better the search - * performance, but the lower the recall. - */ - uint32_t pq_bits = 8; - /** - * The dimensionality of the vector after compression by PQ. - * When zero, an optimal value is selected using a heuristic. - * - * TODO: at the moment `dim` must be a multiple `pq_dim`. - */ - uint32_t pq_dim = 0; - /** - * Vector Quantization (VQ) codebook size - number of "coarse cluster centers". - * When zero, an optimal value is selected using a heuristic. - */ - uint32_t vq_n_centers = 0; - /** The number of iterations searching for kmeans centers (both VQ & PQ phases). */ - uint32_t kmeans_n_iters = 25; - /** - * The fraction of data to use during iterative kmeans building (VQ phase). - * When zero, an optimal value is selected using a heuristic. - */ - double vq_kmeans_trainset_fraction = 0; - /** - * The fraction of data to use during iterative kmeans building (PQ phase). - * When zero, an optimal value is selected using a heuristic. - */ - double pq_kmeans_trainset_fraction = 0; - - /** Build a raft CAGRA index params from an existing cuvs CAGRA index params. */ - operator raft::neighbors::vpq_params() const - { - return {.pq_bits = pq_bits, - .pq_dim = pq_dim, - .vq_n_centers = vq_n_centers, - .kmeans_n_iters = kmeans_n_iters, - .vq_kmeans_trainset_fraction = vq_kmeans_trainset_fraction, - .pq_kmeans_trainset_fraction = pq_kmeans_trainset_fraction}; - } -}; - struct index_params : ann::index_params { /** Degree of input graph for pruning. */ size_t intermediate_graph_degree = 128; @@ -106,23 +61,7 @@ struct index_params : ann::index_params { * * NOTE: this is experimental new API, consider it unsafe. */ - std::optional compression = std::nullopt; - - /** Build a raft CAGRA index params from an existing cuvs CAGRA index params. */ - operator raft::neighbors::cagra::index_params() const - { - return raft::neighbors::cagra::index_params{ - { - .metric = static_cast((int)this->metric), - .metric_arg = this->metric_arg, - .add_data_on_build = this->add_data_on_build, - }, - .intermediate_graph_degree = intermediate_graph_degree, - .graph_degree = graph_degree, - .build_algo = static_cast((int)build_algo), - .nn_descent_niter = nn_descent_niter, - .compression = compression}; - } + std::optional compression = std::nullopt; }; /** @@ -187,27 +126,6 @@ struct search_params : ann::search_params { uint32_t num_random_samplings = 1; /** Bit mask used for initial random seed node selection. */ uint64_t rand_xor_mask = 0x128394; - - /** Build a raft CAGRA search params from an existing cuvs CAGRA search params. */ - operator raft::neighbors::cagra::search_params() const - { - raft::neighbors::cagra::search_params result = { - {}, - max_queries, - itopk_size, - max_iterations, - static_cast((int)algo), - team_size, - search_width, - min_iterations, - thread_block_size, - static_cast((int)hashmap_mode), - hashmap_min_bitlen, - hashmap_max_fill_rate, - num_random_samplings, - rand_xor_mask}; - return result; - } }; /** @@ -232,12 +150,6 @@ static_assert(std::is_aggregate_v); */ template struct index : ann::index { - /** Build a cuvs CAGRA index from an existing RAFT CAGRA index. */ - index(raft::neighbors::cagra::index&& raft_idx) - : ann::index(), - raft_index_{std::make_unique>(std::move(raft_idx))} - { - } static_assert(!raft::is_narrowing_v, "IdxT must be able to represent all values of uint32_t"); @@ -245,35 +157,50 @@ struct index : ann::index { /** Distance metric used for clustering. */ [[nodiscard]] constexpr inline auto metric() const noexcept -> cuvs::distance::DistanceType { - return static_cast((int)raft_index_->metric()); + return metric_; } /** Total length of the index (number of vectors). */ - [[nodiscard]] constexpr inline auto size() const noexcept -> IdxT { return raft_index_->size(); } - - /** Dimensionality of the data. */ - [[nodiscard]] constexpr inline auto dim() const noexcept -> uint32_t + [[nodiscard]] constexpr inline auto size() const noexcept -> IdxT { - return raft_index_->dim(); + auto data_rows = dataset_->n_rows(); + return data_rows > 0 ? data_rows : graph_view_.extent(0); } + + /** Dimensionality of the data. */ + [[nodiscard]] constexpr inline auto dim() const noexcept -> uint32_t { return dataset_->dim(); } /** Graph degree */ [[nodiscard]] constexpr inline auto graph_degree() const noexcept -> uint32_t { - return raft_index_->graph_degree(); + return graph_view_.extent(1); } - /** Dataset [size, dim] */ - [[nodiscard]] inline auto dataset() const noexcept + /** + * DEPRECATED: please use data() instead. + * If you need to query dataset dimensions, use the dim() and size() of the cagra index. + * The data_handle() is not always available: you need to do a dynamic_cast to the expected + * dataset type at runtime. + */ + [[nodiscard]] [[deprecated("Use data()")]] inline auto dataset() const noexcept -> raft::device_matrix_view { - return raft_index_->dataset(); + auto p = dynamic_cast*>(dataset_.get()); + if (p != nullptr) { return p->view(); } + auto d = dataset_->dim(); + return raft::make_device_strided_matrix_view(nullptr, 0, d, d); + } + + /** Dataset [size, dim] */ + [[nodiscard]] inline auto data() const noexcept -> const cuvs::neighbors::dataset& + { + return *dataset_; } /** neighborhood graph [size, graph-degree] */ [[nodiscard]] inline auto graph() const noexcept -> raft::device_matrix_view { - return raft_index_->graph(); + return graph_view_; } // Don't allow copying the index for performance reasons (try avoiding copying data) @@ -287,10 +214,12 @@ struct index : ann::index { index(raft::resources const& res, cuvs::distance::DistanceType metric = cuvs::distance::DistanceType::L2Expanded) : ann::index(), - raft_index_(std::make_unique>( - res, static_cast((int)metric))) + metric_(metric), + graph_(raft::make_device_matrix(res, 0, 0)), + dataset_(new cuvs::neighbors::empty_dataset(0)) { } + /** Construct an index from dataset and knn_graph arrays * * If the dataset and graph is already in GPU memory, then the index is just a thin wrapper around @@ -306,7 +235,7 @@ struct index : ann::index { * * - Cagra index is normally created by the cagra::build * @code{.cpp} - * using namespace cuvs::neighbors::experimental; + * using namespace raft::neighbors::experimental; * auto dataset = raft::make_host_matrix(n_rows, n_cols); * load_dataset(dataset.view()); * // use default index parameters @@ -322,11 +251,11 @@ struct index : ann::index { * @endcode * In the above example, we have passed a host dataset to build. The returned index will own a * device copy of the dataset and the knn_graph. In contrast, if we pass the dataset as a - * raft::device_mdspan to build, then it will only store a reference to it. + * device_mdspan to build, then it will only store a reference to it. * * - Constructing index using existing knn-graph * @code{.cpp} - * using namespace cuvs::neighbors::experimental; + * using namespace raft::neighbors::experimental; * * auto dataset = raft::make_device_matrix(res, n_rows, n_cols); * auto knn_graph = raft::make_device_matrix(res, n_rows, graph_degree); @@ -352,14 +281,17 @@ struct index : ann::index { raft::mdspan, raft::row_major, graph_accessor> knn_graph) : ann::index(), - raft_index_(std::make_unique>( - res, static_cast((int)metric), dataset, knn_graph)) + metric_(metric), + graph_(raft::make_device_matrix(res, 0, 0)), + dataset_(make_aligned_dataset(res, dataset, 16)) { RAFT_EXPECTS(dataset.extent(0) == knn_graph.extent(0), "Dataset and knn_graph must have equal number of rows"); - update_dataset(res, dataset); update_graph(res, knn_graph); + + printf("Called update_graph\n"); raft::resource::sync_stream(res); + printf("Done syncing\n"); } /** @@ -372,8 +304,16 @@ struct index : ann::index { void update_dataset(raft::resources const& res, raft::device_matrix_view dataset) { - raft_index_->update_dataset(res, dataset); + dataset_ = make_aligned_dataset(res, dataset, 16); } + + /** Set the dataset reference explicitly to a device matrix view with padding. */ + void update_dataset(raft::resources const& res, + raft::device_matrix_view dataset) + { + dataset_ = make_aligned_dataset(res, dataset, 16); + } + /** * Replace the dataset with a new dataset. * @@ -382,7 +322,22 @@ struct index : ann::index { void update_dataset(raft::resources const& res, raft::host_matrix_view dataset) { - raft_index_->update_dataset(res, dataset); + dataset_ = make_aligned_dataset(res, dataset, 16); + } + + /** Replace the dataset with a new dataset. */ + template + auto update_dataset(raft::resources const& res, DatasetT&& dataset) + -> std::enable_if_t, DatasetT>> + { + dataset_ = std::make_unique(std::move(dataset)); + } + + template + auto update_dataset(raft::resources const& res, std::unique_ptr&& dataset) + -> std::enable_if_t, DatasetT>> + { + dataset_ = std::move(dataset); } /** @@ -394,7 +349,7 @@ struct index : ann::index { void update_graph(raft::resources const& res, raft::device_matrix_view knn_graph) { - raft_index_->update_graph(res, knn_graph); + graph_view_ = knn_graph; } /** @@ -405,19 +360,29 @@ struct index : ann::index { void update_graph(raft::resources const& res, raft::host_matrix_view knn_graph) { - raft_index_->update_graph(res, knn_graph); - } - - auto get_raft_index() const -> const raft::neighbors::cagra::index* - { - return raft_index_.get(); + RAFT_LOG_DEBUG("Copying CAGRA knn graph from host to device"); + + printf("Copying graph...\n"); + if ((graph_.extent(0) != knn_graph.extent(0)) || (graph_.extent(1) != knn_graph.extent(1))) { + // clear existing memory before allocating to prevent OOM errors on large graphs + if (graph_.size()) { graph_ = raft::make_device_matrix(res, 0, 0); } + graph_ = + raft::make_device_matrix(res, knn_graph.extent(0), knn_graph.extent(1)); + } + raft::copy(graph_.data_handle(), + knn_graph.data_handle(), + knn_graph.size(), + raft::resource::get_cuda_stream(res)); + graph_view_ = graph_.view(); + printf("Done...\n"); } - auto get_raft_index() -> raft::neighbors::cagra::index* { return raft_index_.get(); } private: - std::unique_ptr> raft_index_; + cuvs::distance::DistanceType metric_; + raft::device_matrix graph_; + raft::device_matrix_view graph_view_; + std::unique_ptr> dataset_; }; - /** * @} */ diff --git a/cpp/include/cuvs/neighbors/dataset.hpp b/cpp/include/cuvs/neighbors/dataset.hpp new file mode 100644 index 0000000000..79c3f644f9 --- /dev/null +++ b/cpp/include/cuvs/neighbors/dataset.hpp @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include // get_device_for_address +#include // rounding up + +#include +#include +#include + +#ifdef __cpp_lib_bitops +#include +#endif + +namespace cuvs::neighbors { + +/** Two-dimensional dataset; maybe owning, maybe compressed, maybe strided. */ +template +struct dataset { + using index_type = IdxT; + /** Size of the dataset. */ + [[nodiscard]] virtual auto n_rows() const noexcept -> index_type = 0; + /** Dimensionality of the dataset. */ + [[nodiscard]] virtual auto dim() const noexcept -> uint32_t = 0; + /** Whether the object owns the data. */ + [[nodiscard]] virtual auto is_owning() const noexcept -> bool = 0; + virtual ~dataset() noexcept = default; +}; + +template +struct empty_dataset : public dataset { + using index_type = IdxT; + uint32_t suggested_dim; + explicit empty_dataset(uint32_t dim) noexcept : suggested_dim(dim) {} + [[nodiscard]] auto n_rows() const noexcept -> index_type final { return 0; } + [[nodiscard]] auto dim() const noexcept -> uint32_t final { return suggested_dim; } + [[nodiscard]] auto is_owning() const noexcept -> bool final { return true; } +}; + +template +struct strided_dataset : public dataset { + using index_type = IdxT; + using value_type = DataT; + using view_type = raft::device_matrix_view; + [[nodiscard]] auto n_rows() const noexcept -> index_type final { return view().extent(0); } + [[nodiscard]] auto dim() const noexcept -> uint32_t final + { + return static_cast(view().extent(1)); + } + /** Leading dimension of the dataset. */ + [[nodiscard]] constexpr auto stride() const noexcept -> uint32_t + { + auto v = view(); + return static_cast(v.stride(0) > 0 ? v.stride(0) : v.extent(1)); + } + /** Get the view of the data. */ + [[nodiscard]] virtual auto view() const noexcept -> view_type = 0; +}; + +template +struct non_owning_dataset : public strided_dataset { + using index_type = IdxT; + using value_type = DataT; + using typename strided_dataset::view_type; + view_type data; + explicit non_owning_dataset(view_type v) noexcept : data(v) {} + [[nodiscard]] auto is_owning() const noexcept -> bool final { return false; } + [[nodiscard]] auto view() const noexcept -> view_type final { return data; }; +}; + +template +struct owning_dataset : public strided_dataset { + using index_type = IdxT; + using value_type = DataT; + using typename strided_dataset::view_type; + using storage_type = + raft::mdarray, LayoutPolicy, ContainerPolicy>; + using mapping_type = typename view_type::mapping_type; + storage_type data; + mapping_type view_mapping; + owning_dataset(storage_type&& store, mapping_type view_mapping) noexcept + : data{std::move(store)}, view_mapping{view_mapping} + { + } + + [[nodiscard]] auto is_owning() const noexcept -> bool final { return true; } + [[nodiscard]] auto view() const noexcept -> view_type final + { + return view_type{data.data_handle(), view_mapping}; + }; +}; + +/** + * @brief Contstruct a strided matrix from any mdarray or mdspan. + * + * This function constructs a non-owning view if the input satisfied two conditions: + * + * 1) The data is accessible from the current device + * 2) The memory layout is the same as expected (row-major matrix with the required stride) + * + * Otherwise, this function constructs an owning device matrix and copies the data. + * When the data is copied, padding elements are filled with zeroes. + * + * @tparam SrcT the source mdarray or mdspan + * + * @param[in] res raft resources handle + * @param[in] src the source mdarray or mdspan + * @param[in] required_stride the leading dimension (in elements) + * @return maybe owning current-device-accessible strided matrix + */ +template +auto make_strided_dataset(const raft::resources& res, const SrcT& src, uint32_t required_stride) + -> std::unique_ptr> +{ + using extents_type = typename SrcT::extents_type; + using value_type = typename SrcT::value_type; + using index_type = typename SrcT::index_type; + using layout_type = typename SrcT::layout_type; + static_assert(extents_type::rank() == 2, "The input must be a matrix."); + static_assert(std::is_same_v || + std::is_same_v> || + std::is_same_v, + "The input must be row-major"); + RAFT_EXPECTS(src.extent(1) <= required_stride, + "The input row length must be not larger than the desired stride."); + cudaPointerAttributes ptr_attrs; + RAFT_CUDA_TRY(cudaPointerGetAttributes(&ptr_attrs, src.data_handle())); + auto* device_ptr = reinterpret_cast(ptr_attrs.devicePointer); + const uint32_t src_stride = src.stride(0) > 0 ? src.stride(0) : src.extent(1); + const bool device_accessible = device_ptr != nullptr; + const bool row_major = src.stride(1) <= 1; + const bool stride_matches = required_stride == src_stride; + + if (device_accessible && row_major && stride_matches) { + // Everything matches: make a non-owning dataset + return std::make_unique>( + raft::make_device_strided_matrix_view( + device_ptr, src.extent(0), src.extent(1), required_stride)); + } + // Something is wrong: have to make a copy and produce an owning dataset + auto out_layout = + raft::make_strided_layout(src.extents(), std::array{required_stride, 1}); + auto out_array = + raft::make_device_matrix(res, src.extent(0), required_stride); + + using out_mdarray_type = decltype(out_array); + using out_layout_type = typename out_mdarray_type::layout_type; + using out_container_policy_type = typename out_mdarray_type::container_policy_type; + using out_owning_type = + owning_dataset; + + RAFT_CUDA_TRY(cudaMemsetAsync(out_array.data_handle(), + 0, + out_array.size() * sizeof(value_type), + raft::resource::get_cuda_stream(res))); + RAFT_CUDA_TRY(cudaMemcpy2DAsync(out_array.data_handle(), + sizeof(value_type) * required_stride, + src.data_handle(), + sizeof(value_type) * src_stride, + sizeof(value_type) * src.extent(1), + src.extent(0), + cudaMemcpyDefault, + raft::resource::get_cuda_stream(res))); + + return std::make_unique(std::move(out_array), out_layout); +} + +/** + * @brief Contstruct a strided matrix from any mdarray or mdspan. + * + * A variant `make_strided_dataset` that allows specifying the byte alignment instead of the + * explicit stride length. + * + * @tparam SrcT the source mdarray or mdspan + * + * @param[in] res raft resources handle + * @param[in] src the source mdarray or mdspan + * @param[in] align_bytes the required byte alignment for the dataset rows. + * @return maybe owning current-device-accessible strided matrix + */ +template +auto make_aligned_dataset(const raft::resources& res, const SrcT& src, uint32_t align_bytes = 16) + -> std::unique_ptr> +{ + using value_type = typename SrcT::value_type; + constexpr size_t kSize = sizeof(value_type); + uint32_t required_stride = + raft::round_up_safe(src.extent(1) * kSize, std::lcm(align_bytes, kSize)) / kSize; + return make_strided_dataset(res, src, required_stride); +} + +/** Parameters for VPQ compression. */ +using vpq_params = cuvs::neighbors::ann::vpq_params; + +/** + * @brief VPQ compressed dataset. + * + * The dataset is compressed using two level quantization + * + * 1. Vector Quantization + * 2. Product Quantization of residuals + * + * @tparam MathT the type of elements in the codebooks + * @tparam IdxT type of the vector indices (represent dataset.extent(0)) + * + */ +template +struct vpq_dataset : public dataset { + /** Vector Quantization codebook - "coarse cluster centers". */ + raft::device_matrix vq_code_book; + /** Product Quantization codebook - "fine cluster centers". */ + raft::device_matrix pq_code_book; + /** Compressed dataset. */ + raft::device_matrix data; + + vpq_dataset(raft::device_matrix&& vq_code_book, + raft::device_matrix&& pq_code_book, + raft::device_matrix&& data) + : vq_code_book{std::move(vq_code_book)}, + pq_code_book{std::move(pq_code_book)}, + data{std::move(data)} + { + } + + [[nodiscard]] auto n_rows() const noexcept -> IdxT final { return data.extent(0); } + [[nodiscard]] auto dim() const noexcept -> uint32_t final { return vq_code_book.extent(1); } + [[nodiscard]] auto is_owning() const noexcept -> bool final { return true; } + + /** Row length of the encoded data in bytes. */ + [[nodiscard]] constexpr inline auto encoded_row_length() const noexcept -> uint32_t + { + return data.extent(1); + } + /** The number of "coarse cluster centers" */ + [[nodiscard]] constexpr inline auto vq_n_centers() const noexcept -> uint32_t + { + return vq_code_book.extent(0); + } + /** The bit length of an encoded vector element after compression by PQ. */ + [[nodiscard]] constexpr inline auto pq_bits() const noexcept -> uint32_t + { + /* + NOTE: pq_bits and the book size + + Normally, we'd store `pq_bits` as a part of the index. + However, we know there's an invariant `pq_n_centers = 1 << pq_bits`, i.e. the codebook size is + the same as the number of possible code values. Hence, we don't store the pq_bits and derive it + from the array dimensions instead. + */ + auto pq_width = pq_n_centers(); +#ifdef __cpp_lib_bitops + return std::countr_zero(pq_width); +#else + uint32_t pq_bits = 0; + while (pq_width > 1) { + pq_bits++; + pq_width >>= 1; + } + return pq_bits; +#endif + } + /** The dimensionality of an encoded vector after compression by PQ. */ + [[nodiscard]] constexpr inline auto pq_dim() const noexcept -> uint32_t + { + return raft::div_rounding_up_unsafe(dim(), pq_len()); + } + /** Dimensionality of a subspaces, i.e. the number of vector components mapped to a subspace */ + [[nodiscard]] constexpr inline auto pq_len() const noexcept -> uint32_t + { + return pq_code_book.extent(1); + } + /** The number of vectors in a PQ codebook (`1 << pq_bits`). */ + [[nodiscard]] constexpr inline auto pq_n_centers() const noexcept -> uint32_t + { + return pq_code_book.extent(0); + } +}; + +} // namespace cuvs::neighbors diff --git a/cpp/include/cuvs/neighbors/ivf_list.hpp b/cpp/include/cuvs/neighbors/ivf_list.hpp new file mode 100644 index 0000000000..a12be19638 --- /dev/null +++ b/cpp/include/cuvs/neighbors/ivf_list.hpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace cuvs::neighbors::ivf { + +/** + * Default value filled in the `indices` array. + * One may encounter it trying to access a record within a list that is outside of the + * `size` bound or whenever the list is allocated but not filled-in yet. + */ +template +constexpr static IdxT kInvalidRecord = + (std::is_signed_v ? IdxT{0} : std::numeric_limits::max()) - 1; + +/** The data for a single IVF list. */ +template