mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-11 22:50:00 +00:00
Pin faiss-gpu-cuvs RAPIDS deps to the 26.06 build series (#5643)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5643 `faiss-gpu-cuvs` 1.15.0 fails at import on a clean install: ``` ImportError: libfaiss.so: undefined symbol: _ZTIN3rmm9bad_allocE ``` ## Root cause, measured RAPIDS 26.08 wrapped RMM in a per-release ABI inline namespace. `nm -D` on the published `librmm-cu13` wheels: | Version | `typeinfo for rmm::bad_alloc` | Exported instead | | --- | --- | --- | | 26.2.0 | present | | | 26.4.0 | present | | | 26.6.0 | present | | | 26.8.0 | **absent** | `typeinfo for rmm::_RMM_26_8::bad_alloc` | `pyproject-gpu-cuvs.toml` builds against `==26.06.*` in `before-all` but declares `>=26.06,<27` at runtime and in `test-requires`. A fresh install therefore pairs a 26.06-built `libfaiss.so` with a 26.8 `librmm.so` that renamed every `rmm::` symbol. Nothing vendored covers the gap: `repair-wheel-command` hands every RAPIDS `lib*.so*` under `/opt/cuvs` to `auditwheel --exclude`. The 1.15.0 wheel was uploaded 2026-08-03, two days before RAPIDS 26.08 reached PyPI. It published green and began failing for every new installer afterwards, which matches `build-pip-gpu / Build & test faiss-gpu-cuvs` going red on 2026-08-05 after 68 consecutive passes. An exact-series pin rather than a narrower range is required: the inline namespace encodes the release, so **any** range spanning more than one RAPIDS minor series is broken by construction. ## This diff - Pin `libcuvs-cu13`, `libraft-cu13`, and `librmm-cu13` to `==26.06.*` in both `dependencies` and `test-requires`. - Declare `libraft-cu13`. `faiss/python/__init__.py` imports `libraft` and calls `libraft.load_library()`, but only `before-all` installed it, so at runtime its version floated on `libcuvs-cu13` transitive metadata. - Correct the cuSOLVER soname in the `auditwheel` exclude list from `libcusolver.so.11` to `libcusolver.so.12`, which is what CUDA 13.2 ships. - Update the `_preload_gpu_libs` fix-it message, which told users to install the range this diff removes. - Add `TestCuvsRapidsPinning` to `tests/test_wheel_smoke_gpu.py`. ## Regression guard `tests/test_wheel_smoke_gpu.py` is already the `test-command` for the cuVS wheel, so the new class runs inside `build-pip-gpu.yml` with no workflow change and no new test file. It asserts three properties of the built wheel's own `Requires-Dist`: 1. every RAPIDS distribution `_preload_gpu_libs` dlopens is declared 2. each is pinned to `==26.06.*` rather than an open range 3. the installed versions sit in that series `_RAPIDS_SERIES` duplicates the series from `pyproject-gpu-cuvs.toml` because the wheel does not ship that file. The duplication is the tripwire: a RAPIDS bump has to touch both places deliberately. ## Prior attempt D117099095 made the version-pin change on 2026-08-23 and was abandoned on 2026-09-04 without landing. It never failed review. `faiss/public_tld/` is ShipIt-mirrored with `requireExportConfig`, so `github-export-checks` held it Unpublished pending a `facebookresearch/faiss` PR that a bot cannot open, and three pings over 12 days went unanswered. This diff needs that same export. ## Notes The published 1.15.0 metadata declares only `libcuvs-cu13`, not `librmm-cu13`. That is not a fbsource/GitHub divergence: `librmm-cu13` was added to `dependencies` on 2026-08-14, after the 08-03 release, and has never shipped. `rapids-logger` is installed by `before-all` but stays undeclared here. `librmm-cu13` owns that link-time dependency and carries it in its own metadata; an unversioned duplicate would not make the resolve safer. Reviewed By: mnorris11 Differential Revision: D120162004 fbshipit-source-id: eb6dc316a4fba2a4228eab62d7b86ac8ea94a1b7
This commit is contained in:
committed by
meta-codesync[bot]
parent
7c3b55c01b
commit
3bb48643ab
@@ -121,7 +121,8 @@ def _preload_gpu_libs():
|
||||
raise RuntimeError(
|
||||
"faiss-gpu-cuvs installed but the cuVS runtime wheels are "
|
||||
"missing — "
|
||||
"pip install 'libcuvs-cu13>=26.06,<27' "
|
||||
"pip install 'libcuvs-cu13==26.06.*' 'libraft-cu13==26.06.*' "
|
||||
"'librmm-cu13==26.06.*' "
|
||||
"--extra-index-url https://pypi.nvidia.com"
|
||||
) from e
|
||||
|
||||
|
||||
+11
-5
@@ -44,8 +44,13 @@ dependencies = [
|
||||
"nvidia-cublas>=13.2,<14",
|
||||
"nvidia-curand>=10.3.7,<11",
|
||||
"nvidia-nvjitlink>=13.2,<14",
|
||||
"libcuvs-cu13>=26.06,<27",
|
||||
"librmm-cu13>=26.06,<27",
|
||||
# Exact-series pin, not a range: RAPIDS >=26.08 wraps RMM in a per-release
|
||||
# inline namespace (rmm::_RMM_26_8::), so each minor series renames the
|
||||
# symbols libfaiss.so is linked against. libraft is listed because
|
||||
# _preload_gpu_libs dlopens it directly.
|
||||
"libcuvs-cu13==26.06.*",
|
||||
"libraft-cu13==26.06.*",
|
||||
"librmm-cu13==26.06.*",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -114,8 +119,9 @@ test-requires = [
|
||||
"nvidia-cublas>=13.2,<14",
|
||||
"nvidia-curand>=10.3.7,<11",
|
||||
"nvidia-nvjitlink>=13.2,<14",
|
||||
"libcuvs-cu13>=26.06,<27",
|
||||
"librmm-cu13>=26.06,<27",
|
||||
"libcuvs-cu13==26.06.*",
|
||||
"libraft-cu13==26.06.*",
|
||||
"librmm-cu13==26.06.*",
|
||||
]
|
||||
test-command = "python -m pytest {project}/tests/test_wheel_smoke_gpu.py -v"
|
||||
|
||||
@@ -135,7 +141,7 @@ before-all = "dnf install -y gcc-toolset-14 epel-release && dnf install -y openb
|
||||
# auditwheel excludes (not vendored; supplied at runtime by nvidia-* / libcuvs-cu13
|
||||
# wheels): host driver libcuda, CUDA runtime/math libs (cusparse/cusolver are explicit —
|
||||
# under the cuda-toolkit RPM, not /opt/cuvs), and every RAPIDS .so found under /opt/cuvs.
|
||||
repair-wheel-command = "bash -c 'EXCLUDES=\"--exclude libcuda.so.1 --exclude libcudart.so.13 --exclude libcublas.so.13 --exclude libcublasLt.so.13 --exclude libcurand.so.10 --exclude libnvJitLink.so.13 --exclude libcusparse.so.12 --exclude libcusolver.so.11\"; for f in $(find /opt/cuvs -name \"lib*.so*\" -printf \"%f\\n\" | sort -u); do EXCLUDES=\"$EXCLUDES --exclude $f\"; done; auditwheel repair -w {dest_dir} {wheel} $EXCLUDES'"
|
||||
repair-wheel-command = "bash -c 'EXCLUDES=\"--exclude libcuda.so.1 --exclude libcudart.so.13 --exclude libcublas.so.13 --exclude libcublasLt.so.13 --exclude libcurand.so.10 --exclude libnvJitLink.so.13 --exclude libcusparse.so.12 --exclude libcusolver.so.12\"; for f in $(find /opt/cuvs -name \"lib*.so*\" -printf \"%f\\n\" | sort -u); do EXCLUDES=\"$EXCLUDES --exclude $f\"; done; auditwheel repair -w {dest_dir} {wheel} $EXCLUDES'"
|
||||
# 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)"
|
||||
|
||||
@@ -18,8 +18,12 @@ runs separately in the cmake-based CI.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import importlib.metadata
|
||||
import re
|
||||
import unittest
|
||||
import numpy as np
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.specifiers import SpecifierSet
|
||||
|
||||
import faiss
|
||||
|
||||
@@ -247,3 +251,104 @@ class TestCuvsCagra(unittest.TestCase):
|
||||
self.assertEqual(I.shape, (nq, 10))
|
||||
# CAGRA is approximate, so allow itself-as-first-NN as the contract.
|
||||
np.testing.assert_array_equal(I[:, 0], np.arange(nq))
|
||||
|
||||
|
||||
# Keep in step with the `libcuvs-cu13==<series>.*` pins in
|
||||
# pyproject-gpu-cuvs.toml. The wheel does not ship that file, so nothing else
|
||||
# ties the two together. Compared via SpecifierSet, never by string prefix:
|
||||
# PEP 440 strips the leading zero, so the series pinned as `26.06` installs as
|
||||
# `26.6.0`.
|
||||
_RAPIDS_SERIES = SpecifierSet("==26.06.*")
|
||||
|
||||
# RAPIDS >=26.08 wraps RMM in a per-release inline namespace
|
||||
# (rmm::_RMM_26_8::), so a neighbouring series renames every rmm:: symbol
|
||||
# libfaiss.so is linked against. RAPIDS ships six series a year, YY.02 to
|
||||
# YY.12; probing them lets the declared spec be compared against
|
||||
# _RAPIDS_SERIES by behaviour, so bumping the series needs no edit here.
|
||||
_RAPIDS_PROBES = tuple(
|
||||
f"{year}.{month}.0"
|
||||
for year in (25, 26, 27)
|
||||
for month in (2, 4, 6, 8, 10, 12)
|
||||
)
|
||||
|
||||
# RAPIDS distributions faiss dlopens at import; see _preload_gpu_libs in
|
||||
# faiss/python/__init__.py.
|
||||
_RAPIDS_DISTS = ("librmm-cu13", "libraft-cu13", "libcuvs-cu13")
|
||||
|
||||
|
||||
def _canonical(name):
|
||||
return re.sub(r"[-_.]+", "-", name).lower()
|
||||
|
||||
|
||||
def _cuvs_wheel_requirements():
|
||||
"""Requires-Dist of the installed faiss-gpu-cuvs wheel, grouped by
|
||||
canonical name, or None when faiss did not come from that wheel.
|
||||
|
||||
Values are lists: one name can appear several times under different
|
||||
markers or extras, and collapsing them would hide a bad pin.
|
||||
"""
|
||||
try:
|
||||
specs = importlib.metadata.requires("faiss-gpu-cuvs")
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
return None
|
||||
grouped = {}
|
||||
for req in map(Requirement, specs or ()):
|
||||
grouped.setdefault(_canonical(req.name), []).append(req)
|
||||
return grouped
|
||||
|
||||
|
||||
_CUVS_WHEEL_REQS = _cuvs_wheel_requirements() if _CUVS_BUILD else None
|
||||
_skip_unless_cuvs_wheel = unittest.skipUnless(
|
||||
_CUVS_WHEEL_REQS is not None,
|
||||
"Skipping cuVS packaging tests: not a faiss-gpu-cuvs wheel install",
|
||||
)
|
||||
|
||||
|
||||
@_skip_unless_cuvs_wheel
|
||||
class TestCuvsRapidsPinning(unittest.TestCase):
|
||||
"""Catch: RAPIDS build/runtime skew, undeclared RAPIDS deps.
|
||||
|
||||
The wheel is compiled against one RAPIDS series but resolves RAPIDS at
|
||||
install time. A declared range wider than that series lets the installer
|
||||
pick a librmm that no longer exports the symbols libfaiss.so was linked
|
||||
against; `import faiss` then dies during collection, before any other
|
||||
test in this file runs.
|
||||
"""
|
||||
|
||||
def _declared(self, dist):
|
||||
reqs = _CUVS_WHEEL_REQS.get(_canonical(dist))
|
||||
self.assertTrue(
|
||||
reqs,
|
||||
f"{dist} is dlopened at import but not declared by the wheel, "
|
||||
f"so its version floats on other packages' metadata",
|
||||
)
|
||||
return reqs
|
||||
|
||||
def test_preloaded_rapids_dists_are_declared(self):
|
||||
for dist in _RAPIDS_DISTS:
|
||||
self._declared(dist)
|
||||
|
||||
def test_rapids_dists_admit_only_the_build_series(self):
|
||||
for dist in _RAPIDS_DISTS:
|
||||
for req in self._declared(dist):
|
||||
for probe in _RAPIDS_PROBES:
|
||||
self.assertEqual(
|
||||
_RAPIDS_SERIES.contains(probe),
|
||||
req.specifier.contains(probe),
|
||||
f"{dist} spec '{req.specifier}' disagrees with the "
|
||||
f"build series {_RAPIDS_SERIES} on RAPIDS {probe}; "
|
||||
f"a spec spanning series renames the rmm:: symbols "
|
||||
f"libfaiss.so is linked against",
|
||||
)
|
||||
|
||||
def test_installed_rapids_matches_the_build_series(self):
|
||||
for dist in _RAPIDS_DISTS:
|
||||
try:
|
||||
version = importlib.metadata.version(dist)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
self.fail(f"{dist} is declared but not installed")
|
||||
self.assertTrue(
|
||||
_RAPIDS_SERIES.contains(version),
|
||||
f"{dist} {version} is outside the {_RAPIDS_SERIES} series "
|
||||
f"the wheel was built against",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user