mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-11 22:50:00 +00:00
Add faiss-gpu pip wheel packaging (#5131)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5131 Add pip wheel packaging for `faiss-gpu`, enabling `pip install faiss-gpu` from PyPI. Builds GPU wheels for Linux x86_64, Python 3.10-3.13, with CUDA 12.6 on manylinux_2_28. **New files:** - `pyproject-gpu.toml`: scikit-build-core config for faiss-gpu. Enables CUDA GPU support, uses Dynamic Dispatch, OpenBLAS, and excludes CUDA runtime libs (provided by nvidia-cuda-runtime-cu12 pip dependency). - `.github/workflows/build-pip-gpu.yml`: CI workflow for GPU wheels using cibuildwheel on GPU runners (4-core-ubuntu-gpu-t4). - `tests/test_wheel_smoke_gpu.py`: GPU smoke tests validating GPU detection, StandardGpuResources, GPU FlatL2/IP search, IVF+PQ on GPU, CPU<->GPU transfer roundtrip, and GPU serialization. **Modified files:** - `python/__init__.py`: Added `_preload_gpu_libs()` to pre-load CUDA shared libraries from pip packages before SWIG import. No-op on CPU-only installs. - `.github/workflows/build.yml`: Added `build-pip-gpu` workflow call. - `tests/BUCK`: Added `test_wheel_smoke_gpu` target for Buck CI. Reviewed By: junjieqi Differential Revision: D97507525 fbshipit-source-id: f8b5fa08405aa53267e791c2c0d263e2d6190741
This commit is contained in:
committed by
meta-codesync[bot]
parent
09937153dc
commit
5d9aae6ee8
@@ -0,0 +1,97 @@
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
name: Build GPU pip wheel
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
# One run per ref. Tag pushes are not cancelable — a half-canceled release
|
||||
# build leaves PyPI in an inconsistent state. Branch/PR runs supersede.
|
||||
group: gpu-pip-${{ github.ref }}
|
||||
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
jobs:
|
||||
build-faiss-gpu:
|
||||
name: Build & test faiss-gpu
|
||||
runs-on: 4-core-ubuntu-gpu-t4
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Activate GPU pyproject.toml
|
||||
# cibuildwheel + scikit-build-core both require literal pyproject.toml.
|
||||
# Workspace is fresh per job (actions/checkout creates new tree).
|
||||
run: cp pyproject-gpu.toml pyproject.toml
|
||||
|
||||
- name: Set up Python
|
||||
# Pin host Python explicitly: the 4-core-ubuntu-gpu-t4 runner image
|
||||
# ships a broken Python 3.14.4 in its tool cache (libpython3.14.so.1.0
|
||||
# missing), and pypa/cibuildwheel picks that up by default.
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Build wheel (cibuildwheel)
|
||||
uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0
|
||||
env:
|
||||
CIBW_BUILD_VERBOSITY: 1
|
||||
|
||||
- name: Upload wheel
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-gpu
|
||||
path: wheelhouse/*.whl
|
||||
|
||||
publish-gpu:
|
||||
name: Publish faiss-gpu to PyPI
|
||||
needs: [build-faiss-gpu]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/faiss-gpu
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Download wheel
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: wheels-gpu
|
||||
path: dist
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1
|
||||
with:
|
||||
verbose: true
|
||||
attestations: false
|
||||
|
||||
publish-testpypi-gpu:
|
||||
name: Publish faiss-gpu to TestPyPI
|
||||
needs: [build-faiss-gpu]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
environment:
|
||||
name: testpypi
|
||||
url: https://test.pypi.org/p/faiss-gpu
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Download wheel
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: wheels-gpu
|
||||
path: dist
|
||||
- name: Publish to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
skip-existing: true
|
||||
verbose: true
|
||||
@@ -17,3 +17,5 @@ jobs:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
||||
build-pip:
|
||||
uses: ./.github/workflows/build-pip.yml
|
||||
build-pip-gpu:
|
||||
uses: ./.github/workflows/build-pip-gpu.yml
|
||||
|
||||
@@ -442,6 +442,19 @@ install(FILES
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/__init__.pyi")
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/__init__.pyi" DESTINATION faiss)
|
||||
endif()
|
||||
|
||||
# GPU build marker: only present in faiss-gpu wheels. Used by __init__.py to
|
||||
# decide whether to preload CUDA shared libs from nvidia-*-cu12 pip packages.
|
||||
# Gating on this marker (instead of importability of `nvidia.cuda_runtime`)
|
||||
# prevents false positives in faiss-cpu environments where PyTorch or cupy
|
||||
# have transitively pulled in the nvidia-* packages.
|
||||
if(FAISS_ENABLE_GPU)
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/_gpu_build.py"
|
||||
"# Auto-generated when FAISS_ENABLE_GPU=ON.\n"
|
||||
"IS_GPU_BUILD = True\n")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_gpu_build.py" DESTINATION faiss)
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/py.typed")
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/py.typed" DESTINATION faiss)
|
||||
endif()
|
||||
|
||||
@@ -13,6 +13,84 @@ import logging
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
|
||||
def _preload_gpu_libs():
|
||||
"""Pre-load CUDA shared libraries from nvidia-*-cu12 pip packages.
|
||||
|
||||
The libcudart.so / libcublas.so / libcurand.so files live in
|
||||
site-packages/nvidia/{cuda_runtime,cublas,curand}/lib/ — not on the
|
||||
default ld.so search path. Pre-load them with RTLD_GLOBAL before the
|
||||
SWIG extension dlopen()s libfaiss.so (which has CUDA undefined symbols).
|
||||
|
||||
Gating: keyed on the `faiss._gpu_build` module, which CMake writes only
|
||||
when FAISS_ENABLE_GPU=ON. We can't gate on `nvidia.cuda_runtime`
|
||||
importability — PyTorch and cupy CUDA wheels pull that in transitively,
|
||||
so a faiss-cpu user with PyTorch+CUDA would otherwise hit this path and
|
||||
see a misleading error if any nvidia-* package were missing.
|
||||
|
||||
Behavior:
|
||||
- faiss-cpu install (no `_gpu_build` marker): silent no-op.
|
||||
- faiss-gpu install with missing nvidia-cuda-runtime / cublas / curand:
|
||||
raises RuntimeError with fix-it message.
|
||||
"""
|
||||
try:
|
||||
from . import _gpu_build # noqa: F401
|
||||
except ImportError:
|
||||
return # faiss-cpu install: marker absent, nothing to preload
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
|
||||
try:
|
||||
import nvidia.cuda_runtime
|
||||
except ImportError as e:
|
||||
raise RuntimeError(
|
||||
"faiss-gpu installed but nvidia-cuda-runtime-cu12 is missing — "
|
||||
"pip install 'nvidia-cuda-runtime-cu12>=12.6,<13'"
|
||||
) from e
|
||||
|
||||
try:
|
||||
import nvidia.cublas
|
||||
except ImportError as e:
|
||||
raise RuntimeError(
|
||||
"faiss-gpu installed but nvidia-cublas-cu12 is missing — "
|
||||
"pip install 'nvidia-cublas-cu12>=12.6,<13'"
|
||||
) from e
|
||||
|
||||
try:
|
||||
import nvidia.curand
|
||||
except ImportError as e:
|
||||
raise RuntimeError(
|
||||
"faiss-gpu installed but nvidia-curand-cu12 is missing — "
|
||||
"pip install 'nvidia-curand-cu12>=10.3.7,<11'"
|
||||
) from e
|
||||
|
||||
# Order matters: libcudart provides symbols that libcublas* / libcurand depend on.
|
||||
_LIBS = [
|
||||
(nvidia.cuda_runtime, "libcudart.so.12"),
|
||||
(nvidia.cublas, "libcublas.so.12"),
|
||||
(nvidia.cublas, "libcublasLt.so.12"),
|
||||
(nvidia.curand, "libcurand.so.10"),
|
||||
]
|
||||
for pkg, soname in _LIBS:
|
||||
# Use __path__[0] not __file__ — the nvidia-*-cu12 wheels are PEP 420
|
||||
# implicit namespace packages, so pkg.__file__ is None.
|
||||
so_path = os.path.join(pkg.__path__[0], "lib", soname)
|
||||
try:
|
||||
ctypes.CDLL(so_path, mode=ctypes.RTLD_GLOBAL)
|
||||
except OSError as e:
|
||||
raise RuntimeError(
|
||||
f"faiss-gpu: failed to load {soname} from {so_path} — "
|
||||
f"corrupt nvidia-*-cu12 wheel? Try: pip install --force-reinstall "
|
||||
f"'nvidia-cuda-runtime-cu12>=12.6,<13' "
|
||||
f"'nvidia-cublas-cu12>=12.6,<13' "
|
||||
f"'nvidia-curand-cu12>=10.3.7,<11'"
|
||||
) from e
|
||||
|
||||
|
||||
_preload_gpu_libs()
|
||||
del _preload_gpu_libs
|
||||
|
||||
# We import * so that the symbol foo can be accessed as faiss.foo.
|
||||
from .loader import *
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
[build-system]
|
||||
requires = ["scikit-build-core>=0.10", "swig>=4.2,<5", "numpy>=2.0", "cmake>=3.24", "ninja"]
|
||||
build-backend = "scikit_build_core.build"
|
||||
|
||||
[project]
|
||||
name = "faiss-gpu"
|
||||
dynamic = ["version"]
|
||||
description = "A library for efficient similarity search and clustering of dense vectors (GPU support)."
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
license-files = ["LICENSE", "THIRD_PARTY_NOTICES"]
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
{name = "Meta AI Research"},
|
||||
]
|
||||
keywords = ["search", "nearest-neighbors", "clustering", "vectors", "similarity", "gpu", "cuda"]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Programming Language :: C++",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Environment :: GPU :: NVIDIA CUDA :: 12",
|
||||
]
|
||||
dependencies = [
|
||||
"numpy>=1.25",
|
||||
"packaging",
|
||||
"nvidia-cuda-runtime-cu12>=12.6,<13",
|
||||
"nvidia-cublas-cu12>=12.6,<13",
|
||||
"nvidia-curand-cu12>=10.3.7,<11",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/facebookresearch/faiss"
|
||||
Documentation = "https://github.com/facebookresearch/faiss/wiki"
|
||||
Repository = "https://github.com/facebookresearch/faiss"
|
||||
Issues = "https://github.com/facebookresearch/faiss/issues"
|
||||
|
||||
[tool.scikit-build]
|
||||
cmake.build-type = "Release"
|
||||
|
||||
# Pull version from CMakeLists.txt's project(VERSION ...) — single source of truth.
|
||||
metadata.version.provider = "scikit_build_core.metadata.regex"
|
||||
metadata.version.input = "CMakeLists.txt"
|
||||
metadata.version.regex = '^\s+VERSION\s+(?P<value>[0-9]+\.[0-9]+\.[0-9]+)'
|
||||
|
||||
# Exclude C++ headers, cmake configs, static libs, and binaries from the wheel.
|
||||
# Only the Python package (faiss/) and bundled shared libs remain.
|
||||
wheel.exclude = ["include/**", "share/**", "lib/**", "lib64/**", "bin/**"]
|
||||
|
||||
[tool.scikit-build.cmake.define]
|
||||
FAISS_OPT_LEVEL = "dd"
|
||||
FAISS_ENABLE_GPU = "ON"
|
||||
FAISS_ENABLE_CUVS = "OFF"
|
||||
FAISS_ENABLE_PYTHON = "ON"
|
||||
FAISS_ENABLE_MKL = "OFF"
|
||||
FAISS_ENABLE_C_API = "OFF"
|
||||
FAISS_ENABLE_EXTRAS = "OFF"
|
||||
FAISS_ENABLE_SVS = "OFF"
|
||||
FAISS_USE_LTO = "ON"
|
||||
CMAKE_CUDA_ARCHITECTURES = "70-real;75-real;80-real;86-real;89-real;90"
|
||||
CMAKE_CUDA_FLAGS_RELEASE = "-O3 -Xfatbin=-compress-all --threads 4 -Xcompiler=-fdata-sections,-ffunction-sections"
|
||||
CMAKE_C_FLAGS = "-fdata-sections -ffunction-sections"
|
||||
CMAKE_CXX_FLAGS = "-fdata-sections -ffunction-sections"
|
||||
CMAKE_SHARED_LINKER_FLAGS = "-Wl,--gc-sections -Wl,--strip-all"
|
||||
CMAKE_MODULE_LINKER_FLAGS = "-Wl,--gc-sections -Wl,--strip-all"
|
||||
BUILD_TESTING = "OFF"
|
||||
BUILD_SHARED_LIBS = "ON"
|
||||
|
||||
# Stable ABI (abi3): one cp310 wheel covers all 3.10+.
|
||||
# GPU is Linux-only; free-threaded Python (cp3*t) cannot use abi3.
|
||||
[[tool.scikit-build.overrides]]
|
||||
if.platform-system = "linux"
|
||||
if.abi-flags = "^$" # Free-threaded Python does not support stable ABI.
|
||||
wheel.py-api = "cp310"
|
||||
|
||||
[tool.cibuildwheel]
|
||||
# Pass through the host GPU so the smoke test can actually exercise CUDA.
|
||||
# Without this, faiss.get_num_gpus() returns 0 inside the container and all
|
||||
# GPU tests silently skip — masking real packaging / driver / SASS-arch problems.
|
||||
container-engine = { name = "docker", create-args = ["--gpus", "all"] }
|
||||
build = "cp310-manylinux_x86_64"
|
||||
test-requires = [
|
||||
"numpy>=1.25,<3.0",
|
||||
"pytest",
|
||||
"nvidia-cuda-runtime-cu12>=12.6,<13",
|
||||
"nvidia-cublas-cu12>=12.6,<13",
|
||||
"nvidia-curand-cu12>=10.3.7,<11",
|
||||
]
|
||||
test-command = "python -m pytest {project}/tests/test_wheel_smoke_gpu.py -v"
|
||||
|
||||
[tool.cibuildwheel.linux]
|
||||
manylinux-x86_64-image = "manylinux_2_28"
|
||||
# manylinux_2_28 ships gcc-toolset-14; CUDA 12.6 only supports GCC <=13, so install
|
||||
# gcc-toolset-13 explicitly and force the build to use it via CC/CXX/CUDAHOSTCXX below.
|
||||
before-all = "dnf install -y gcc-toolset-13 epel-release && dnf install -y openblas-devel openblas-openmp swig && dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo && dnf install -y --setopt=obsoletes=0 cuda-nvcc-12-6 cuda-cudart-devel-12-6 cuda-cccl-12-6 cuda-profiler-api-12-6 libcublas-devel-12-6 libcurand-devel-12-6"
|
||||
# auditwheel: exclude libcuda.so.1 (host driver, not pip-installable)
|
||||
# and the libcudart/libcublas* shipped via nvidia-*-cu12 wheels (preloaded at import time).
|
||||
repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel} --exclude libcudart.so.12 --exclude libcublas.so.12 --exclude libcublasLt.so.12 --exclude libcurand.so.10 --exclude libcuda.so.1"
|
||||
# Canary: ensure the host GPU is actually visible to the container before tests
|
||||
# run. Without this, smoke tests would silently skip on broken --gpus passthrough.
|
||||
before-test = "nvidia-smi || (echo 'GPU not visible to container; check container-engine create-args' && exit 1)"
|
||||
|
||||
# Run abi3audit on Linux to guarantee Stable ABI compliance.
|
||||
# We use 'append' so that the auditwheel repair-wheel-command from
|
||||
# [tool.cibuildwheel.linux] (with the CUDA --exclude flags) still runs first.
|
||||
[[tool.cibuildwheel.overrides]]
|
||||
select = "*-manylinux*"
|
||||
inherit.repair-wheel-command = "append"
|
||||
repair-wheel-command = "pipx run abi3audit --strict --report {wheel}"
|
||||
|
||||
# Build-tool paths. CUDA from /usr/local/cuda-12.6 (NVIDIA RPM convention);
|
||||
# GCC pinned to toolset-13 because CUDA 12.6 does not support GCC 14.
|
||||
# CUDAHOSTCXX must match CXX or nvcc host TUs get an ABI-mismatched libstdc++.
|
||||
[tool.cibuildwheel.linux.environment]
|
||||
CUDA_HOME = "/usr/local/cuda-12.6"
|
||||
CUDACXX = "/usr/local/cuda-12.6/bin/nvcc"
|
||||
CC = "/opt/rh/gcc-toolset-13/root/usr/bin/gcc"
|
||||
CXX = "/opt/rh/gcc-toolset-13/root/usr/bin/g++"
|
||||
CUDAHOSTCXX = "/opt/rh/gcc-toolset-13/root/usr/bin/g++"
|
||||
@@ -0,0 +1,197 @@
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
"""
|
||||
Wheel smoke tests for faiss-gpu and faiss-gpu-cuvs pip packages.
|
||||
|
||||
These tests validate that a pip-installed GPU wheel is correctly packaged:
|
||||
- CUDA shared libraries load (cudart, cublas)
|
||||
- GPU devices are detected
|
||||
- GPU index creation and search works
|
||||
- GPU <-> CPU index transfer works
|
||||
- cuVS/CAGRA integration works (when compiled with cuVS)
|
||||
|
||||
They are intentionally minimal and fast. The full GPU test suite
|
||||
runs separately in the cmake-based CI.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
import faiss
|
||||
|
||||
# This file is the faiss-gpu wheel smoke test. It also gets collected by
|
||||
# `pytest tests/` in the CPU wheel CI and by Buck's GPU-less test runner.
|
||||
# Skip every class via decorator (rather than `raise SkipTest` at module
|
||||
# scope) so pytest still collects the tests — pytest exits with code 5
|
||||
# ("no tests collected") otherwise, which Buck treats as a fatal.
|
||||
_GPU_BUILD = "GPU" in faiss.get_compile_options()
|
||||
_skip_unless_gpu_build = unittest.skipUnless(
|
||||
_GPU_BUILD, "Skipping faiss-gpu smoke tests: built without GPU support"
|
||||
)
|
||||
|
||||
|
||||
def skip_if_no_gpu(test_func):
|
||||
"""Skip test if no GPU is available."""
|
||||
@functools.wraps(test_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if faiss.get_num_gpus() == 0:
|
||||
raise unittest.SkipTest("No GPU available")
|
||||
return test_func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
@_skip_unless_gpu_build
|
||||
class TestGPUAvailability(unittest.TestCase):
|
||||
"""Catch: CUDA libs not loaded, GPU not detected."""
|
||||
|
||||
def test_compile_options_include_gpu(self):
|
||||
opts = faiss.get_compile_options()
|
||||
self.assertIn(
|
||||
"GPU", opts, f"faiss-gpu wheel missing GPU support: {opts}"
|
||||
)
|
||||
|
||||
def test_gpu_count(self):
|
||||
n = faiss.get_num_gpus()
|
||||
if n < 1:
|
||||
raise unittest.SkipTest(f"No GPU available (count={n})")
|
||||
self.assertGreaterEqual(n, 1)
|
||||
|
||||
|
||||
@_skip_unless_gpu_build
|
||||
class TestGPUResources(unittest.TestCase):
|
||||
"""Catch: CUDA runtime init failure, StandardGpuResources construction."""
|
||||
|
||||
@skip_if_no_gpu
|
||||
def test_standard_gpu_resources(self):
|
||||
res = faiss.StandardGpuResources()
|
||||
# Verify the resource object is usable
|
||||
self.assertIsNotNone(res)
|
||||
|
||||
|
||||
@_skip_unless_gpu_build
|
||||
class TestGPUFlatSearch(unittest.TestCase):
|
||||
"""Catch: GPU kernel launch failures, CUDA memory allocation."""
|
||||
|
||||
@skip_if_no_gpu
|
||||
def test_gpu_flat_l2(self):
|
||||
d, n = 64, 1000
|
||||
np.random.seed(42)
|
||||
xb = np.random.random((n, d)).astype("float32")
|
||||
|
||||
res = faiss.StandardGpuResources()
|
||||
index = faiss.GpuIndexFlatL2(res, d)
|
||||
index.add(xb)
|
||||
self.assertEqual(index.ntotal, n)
|
||||
|
||||
D, I = index.search(xb[:5], 10)
|
||||
self.assertEqual(I.shape, (5, 10))
|
||||
# Nearest neighbor of each vector should be itself
|
||||
np.testing.assert_array_equal(I[:, 0], np.arange(5))
|
||||
# Self-distance should be ~0
|
||||
np.testing.assert_allclose(D[:, 0], 0, atol=1e-5)
|
||||
|
||||
@skip_if_no_gpu
|
||||
def test_gpu_flat_ip(self):
|
||||
d, n = 64, 1000
|
||||
np.random.seed(42)
|
||||
xb = np.random.random((n, d)).astype("float32")
|
||||
faiss.normalize_L2(xb)
|
||||
|
||||
res = faiss.StandardGpuResources()
|
||||
index = faiss.GpuIndexFlatIP(res, d)
|
||||
index.add(xb)
|
||||
|
||||
D, I = index.search(xb[:5], 10)
|
||||
self.assertEqual(I.shape, (5, 10))
|
||||
np.testing.assert_array_equal(I[:, 0], np.arange(5))
|
||||
np.testing.assert_allclose(D[:, 0], 1.0, atol=1e-5)
|
||||
|
||||
|
||||
@_skip_unless_gpu_build
|
||||
class TestGPUIVFPQ(unittest.TestCase):
|
||||
"""Catch: GPU training, IVF + PQ kernel issues."""
|
||||
|
||||
@skip_if_no_gpu
|
||||
def test_gpu_ivfpq(self):
|
||||
d, n = 64, 10000
|
||||
np.random.seed(42)
|
||||
xb = np.random.random((n, d)).astype("float32")
|
||||
|
||||
res = faiss.StandardGpuResources()
|
||||
# Build on CPU first, then transfer to GPU
|
||||
cpu_index = faiss.index_factory(d, "IVF32,PQ8")
|
||||
cpu_index.train(xb)
|
||||
|
||||
gpu_index = faiss.index_cpu_to_gpu(res, 0, cpu_index)
|
||||
gpu_index.add(xb)
|
||||
self.assertEqual(gpu_index.ntotal, n)
|
||||
|
||||
D, I = gpu_index.search(xb[:5], 10)
|
||||
self.assertEqual(I.shape, (5, 10))
|
||||
|
||||
|
||||
@_skip_unless_gpu_build
|
||||
class TestGPUCPUTransfer(unittest.TestCase):
|
||||
"""Catch: GPU<->CPU index serialization, memory transfer."""
|
||||
|
||||
@skip_if_no_gpu
|
||||
def test_cpu_to_gpu_and_back(self):
|
||||
d, n = 64, 1000
|
||||
np.random.seed(42)
|
||||
xb = np.random.random((n, d)).astype("float32")
|
||||
|
||||
# Build CPU index
|
||||
cpu_index = faiss.IndexFlatL2(d)
|
||||
cpu_index.add(xb)
|
||||
D_cpu, I_cpu = cpu_index.search(xb[:5], 10)
|
||||
|
||||
# Transfer to GPU
|
||||
res = faiss.StandardGpuResources()
|
||||
gpu_index = faiss.index_cpu_to_gpu(res, 0, cpu_index)
|
||||
self.assertEqual(gpu_index.ntotal, n)
|
||||
D_gpu, I_gpu = gpu_index.search(xb[:5], 10)
|
||||
|
||||
# Transfer back to CPU
|
||||
cpu_index2 = faiss.index_gpu_to_cpu(gpu_index)
|
||||
self.assertEqual(cpu_index2.ntotal, n)
|
||||
D_back, I_back = cpu_index2.search(xb[:5], 10)
|
||||
|
||||
# Results should match
|
||||
np.testing.assert_array_equal(I_cpu, I_gpu)
|
||||
np.testing.assert_array_equal(I_cpu, I_back)
|
||||
np.testing.assert_allclose(D_cpu, D_gpu, atol=1e-5)
|
||||
np.testing.assert_allclose(D_cpu, D_back, atol=1e-5)
|
||||
|
||||
|
||||
@_skip_unless_gpu_build
|
||||
class TestGPUSerialization(unittest.TestCase):
|
||||
"""Catch: GPU index serialization roundtrip."""
|
||||
|
||||
@skip_if_no_gpu
|
||||
def test_gpu_serialize_roundtrip(self):
|
||||
d = 32
|
||||
np.random.seed(42)
|
||||
xb = np.random.random((100, d)).astype("float32")
|
||||
|
||||
res = faiss.StandardGpuResources()
|
||||
gpu_index = faiss.GpuIndexFlatL2(res, d)
|
||||
gpu_index.add(xb)
|
||||
|
||||
# Transfer to CPU for serialization
|
||||
cpu_index = faiss.index_gpu_to_cpu(gpu_index)
|
||||
data = faiss.serialize_index(cpu_index)
|
||||
self.assertIsInstance(data, np.ndarray)
|
||||
|
||||
# Deserialize and transfer back to GPU
|
||||
cpu_index2 = faiss.deserialize_index(data)
|
||||
gpu_index2 = faiss.index_cpu_to_gpu(res, 0, cpu_index2)
|
||||
self.assertEqual(gpu_index2.ntotal, 100)
|
||||
|
||||
D1, I1 = gpu_index.search(xb[:3], 5)
|
||||
D2, I2 = gpu_index2.search(xb[:3], 5)
|
||||
np.testing.assert_array_equal(I1, I2)
|
||||
np.testing.assert_allclose(D1, D2, atol=1e-5)
|
||||
Reference in New Issue
Block a user