faissR

Home | Installation | Implementation | Examples | Benchmarks | Autotuning | API | NN Methods | Backends | References | JSS Manuscript

Numbered citations in this README refer to the bibliography in References.

The Journal of Statistical Software article and replication sources are in manuscript/jss. Reported comparisons include completed matched-node experiments with seven R interfaces; timings are conditional on the recorded configurations, recall screen, and workload.

faissR provides native nearest-neighbour search, kNN models, and k-means for R workflows that need mandatory FAISS support and optional NVIDIA CUDA/RAPIDS acceleration [1-3,13-16]. The package is intended for CRAN-style source installation: FAISS is required for functional builds, while CUDA and RAPIDS cuVS are optional for CPU-only builds. A machine without CUDA can still install the package from source and use the CPU/FAISS functionality. For NVIDIA GPU users, the GPU stack should be requested explicitly so missing CUDA or RAPIDS libraries are fatal rather than silently producing a CPU-only build.

target_recall selects one of three calibration-informed tiers: 0.90, 0.95, or 0.99. The reported calibration and independent-query experiments use mean identifier-overlap recall@k across sampled queries, with the tier required in every recorded validation repeat. Timing repeats do not constitute new query samples. Comparator pairs are called point-recall-matched when both routes pass their prespecified mean-recall screen. These are empirical operating points, not guarantees for unseen datasets. The optional benchmark inference utilities also support tie-aware query-bootstrap bounds; those bounds must not be inferred for archived rows that contain only point recall.

Exact-family routes use a separate exact-audited classification. Their audit accepts identical neighbor identifiers or an equivalent sorted distance multiset within the frozen numerical tolerance, so a tied kth-neighbor boundary does not create a false failure. Raw set overlap remains diagnostic. Benchmark selection and the empirical oracle use exact_audited || approximate_target_met as their eligibility rule.

Post hoc candidate-set sensitivity is evaluated with cross-fitted leave-one-dataset-out (LOODO) analysis. Every row from one named dataset is removed before a method family is selected from the remaining shape evidence; the choice is then evaluated on the omitted dataset without revision. Outputs report operating-point attainment, selected/oracle time ratio, route-family agreement, abstention, and exact-selection frequency per held-out dataset. The installed full-calibration method = "auto" result is kept in separate diagnostic columns and is not presented as LOODO, installed-policy validation, or out-of-collection evidence. The explicit routes used for cross-fitting do not share the installed Flat/IVF candidate universe.

The compiled CUDA automatic policy is an optional experimental policy calibrated on NVIDIA L40S jobs for cold full-self-search. It checks runtime capabilities but does not retune from a CPU/GPU model string. attr(result, "auto_selection")$hardware_evidence is "calibration_hardware_matched" for a confirmed L40S match and "hardware_extrapolated_unvalidated" for a confirmed different machine. If the runtime or calibration hardware identity cannot be determined, it reports "hardware_unidentified". In either case the static policy remains active and no silent method/device fallback occurs. CUDA method = "auto" emits one visible warning per confirmed different runtime GPU model; suppress it only after review with options(faissR.warn_hardware_extrapolation = FALSE). Request method = "exact" explicitly when exhaustive search is required. The opt-in tuning = "pilot" and "cache" modes tune parameters within an explicit or resolved method on local data; they do not learn a new cross-method routing policy. Full local route-policy construction is not currently a public package feature, so the bundled CUDA selector is an L40S-informed experimental heuristic outside the evaluated environment or workload. It is not the package's principal contribution.

The package code does not depend on Python or conda. Conda/mamba environments can be useful for development or benchmarking because they provide compatible FAISS/RAPIDS libraries, but CRAN and source builds should use normal system headers and libraries discovered by configure.

Main Features

Available Functions

The table below summarizes the public R functions shown on GitHub. Full argument-level details are in the API reference, and the method/backend/metric matrix is in the backend-capabilities page.

Function Main use Backends Return
nn() General nearest-neighbour search over reference/query matrices with Euclidean, cosine, or correlation distance. CPU, FAISS CPU/GPU, native CUDA, and direct RAPIDS cuVS where compiled. A faissR_nn list with 1-based indices, distances, and route/tuning metadata.
nn_gpu() GPU-resident exact-family KNN for downstream CUDA packages. CUDA exact-family routes for method = "auto", "exact", "flat", or "bruteforce" with the three public metrics. A faissR_gpu_knn object with owning device buffers and zero device-to-host result copies.
gpu_knn_to_host() Explicit diagnostic conversion of a GPU-resident KNN result to ordinary R matrices. It is never called automatically by nn_gpu(). Uses the CUDA result handle returned by nn_gpu() or the C-callable GPU API. A host-side faissR_nn list with copied integer indices and numeric distances.
candidate_knn() Exact top-k reranking inside a user-supplied candidate-neighbour matrix. This is useful when another algorithm proposes candidates and faissR should compute the final ordered neighbours. CPU compiled scoring or the native CUDA row-candidate kernel for its documented self-query contract. A faissR_nn list restricted to the supplied candidates.
fast_kmeans() Fast k-means-style clustering with CPU, FAISS, FAISS GPU, or cuVS routes. tuning = "auto" selects deterministic shape-aware defaults for iteration count, starts, and tolerances. CPU/statistics, FAISS CPU/GPU, direct cuVS where compiled. A faissR_kmeans object with cluster assignments, centers, within-cluster summaries, backend/tuning metadata, and convergence diagnostics.
knn() Fit a reusable kNN classifier/regressor, or fit and predict immediately with knn(Xtrain, Ytrain, Xtest). It reuses nn() for neighbour search and can preserve float32 training/query data for supported routes. Same device and method family as nn() for the selected training/prediction route. Without Xtest, a faissR_knn_model; with Xtest, predictions or probabilities depending on type.
predict() S3 method for faissR_knn_model objects. It predicts labels, numeric responses, or class probabilities with type = "response" or "prob". Prediction sends the full query matrix in one batched NN call. Same fitted route where compatible; otherwise rebuilds the same requested route rather than silently switching algorithms. A vector/data frame of predictions or a probability matrix, with the underlying nn() metadata attached.
backend_info() Inspect compiled/runtime backend support and implementation notes. All compiled backend families. A data frame of public backend names, implementation labels, runtime availability, and notes.
nn_capabilities() Preflight check for supported public method/backend/metric combinations. runtime = TRUE adds current-machine availability information. CPU, CUDA, FAISS, and cuVS capability checks. A data frame suitable for benchmark filtering before a large run.
nn_metric_preflight() Inspect metric inputs without running a search. CPU/CUDA contract check. A list of affected one-based row indices and a stable backend action label.
faiss_available() Check whether faissR was compiled and linked against FAISS. FAISS CPU. A single logical value.
faiss_gpu_available() Check whether the linked FAISS build reports GPU support. FAISS GPU. A single logical value.
cuda_available() Check whether native CUDA support was compiled and a CUDA device/runtime is available. Native CUDA. A single logical value.
cuvs_available() Check whether direct RAPIDS cuVS support was compiled and can be loaded. RAPIDS cuVS. A single logical value.

C/C++ Callable Entry Points

faissR also registers stable C-callable entry points with R_RegisterCCallable(). These are for downstream R packages with C/C++ code and are retrieved through the installed <faissR_api.h> header. Downstream packages should add faissR to LinkingTo, include that header, and use its typed getter functions rather than repeating function-pointer signatures.

C-callable name ABI Purpose
faissR_c_api_version int(void) Returns 1 for the current callable ABI. The header helper faissR_c_api_version() lets downstream packages reject an incompatible future ABI before dispatch.
faissR_nn_float32_call (x, k, backend, metric, include_self, n_threads) CPU FAISS Flat float32 KNN route. It accepts ordinary R double matrices or optional float::fl()/float32 matrices and returns the stable host faissR_nn list with double distances.
faissR_nn_float32_call_output (x, k, backend, metric, include_self, n_threads, distances) Same CPU FAISS Flat float32 route, with distances = "double" or "float" to request host distance storage type.
faissR_nn_cuda_tuned_gpu_call (x, k, method, metric, include_self, target_recall) CUDA self-KNN route that keeps result buffers on the GPU for method = "auto", "exact", "flat", or "bruteforce". It returns the same faissR_gpu_knn object shape as nn_gpu(), including CUDA device pointers and device_to_host_result_copies = 0.

The returned GPU handle owns its device buffers. It must remain protected from R garbage collection while downstream code uses indices_ptr or distances_ptr; releasing the handle invalidates those pointers. Host and GPU C-callable results include the same machine-readable distance contract as the R API.

Explicit GPU requests are honest: if a CUDA/cuVS backend is requested and was not compiled or is not available at runtime, faissR reports an error instead of silently running CPU code and labelling it as GPU.

For public nearest-neighbour APIs, backend selects the device family: "cpu" or "cuda". When omitted, it follows the package option, then the environment variable, and finally defaults to CPU. The method argument selects the algorithm, for example method = "grid", method = "ivfpq_fastscan", or method = "cagra". Thus nn(x, backend = "cuda", method = "grid") uses the CUDA grid route, while nn(x, backend = "cpu", method = "cagra") stops because CAGRA is CUDA-only. method = "ivfpq_fastscan" resolves to FAISS FastScan on CPU and direct cuVS 4-bit IVF-PQ on CUDA. CPU and CUDA support Euclidean, cosine, and correlation; cosine is handled by row-normalized float32 L2 search and correlation by row-centering plus row-normalized float32 L2 search before distance conversion. Explicit CUDA requests never silently fall back to CPU. With the default method = "auto", faissR chooses the most appropriate method for the selected backend. With tuning = "auto", approximate methods use deterministic defaults identified for the resolved method; pilot/cache tuning is opt-in with tuning = "cache" or tuning = "pilot". The route selector and deterministic approximate-method tuning rules live in C++; R reads user options and passes them to the compiled policy layer, and results report tuning_source = "cpp" when those rules set method parameters. For CUDA Euclidean self-KNN, the current benchmark-derived auto policy uses IVF for large low-dimensional data, exact FAISS GPU Flat/brute force for the measured small, medium, and high-dimensional accuracy-first shapes, and IVF for very large high-dimensional data only at lower target-recall tiers. CPU Euclidean auto uses exact search for tiny data, FAISS HNSW for most medium and high-dimensional self-KNN, and FAISS IVF for selected large low-dimensional datasets where the tuning sweep showed better speed at the requested recall. The public nearest-neighbour metrics are "euclidean", "cosine", and "correlation"; distance choices belong in metric, not in separate method names. Euclidean results are ordinary L2 distances, not squared L2 values. Correlation is centered cosine distance. For normalized cosine and correlation routes, all-zero cosine rows and constant correlation rows are handled explicitly: two zero-normalized rows have distance 0, while a zero-normalized row versus a nonzero row has distance 1. CPU FAISS Flat uses the exact CPU scorer for this degenerate case to preserve deterministic small-k tie handling; explicit CUDA routes do not perform CPU repair and therefore error clearly for these degenerate normalized rows. The finite CPU values are software conventions for otherwise undefined cosine or correlation cases, not mathematical cosine similarities or Pearson correlations. All backends reject rows containing NA, NaN, Inf, or -Inf. Unsupported method/backend/metric combinations fail without changing the requested metric, method, or device; use nn_capabilities(runtime = TRUE) to preflight both design support and locally available providers. Use nn_metric_preflight() to inspect the data-dependent contract and obtain the affected row indices before starting the requested backend.

Every KNN result makes the value contract machine-readable through distance_is_metric, distance_semantics, distance_comparable_across_queries, and distance_order. GPU-resident nn_gpu() results expose the same contract metadata without copying result buffers to the host. The NN methods guide describes each nearest-neighbour method and cites the relevant algorithm/software references.

The preferred public spellings for faissR-owned candidate-graph refinements are method = "nsg_style", "vamana_style", and "nndescent_style". The historical "nsg", "vamana", and "nndescent" spellings remain compatibility aliases. Here _style is a scope marker, not an algorithm name: the package-owned CPU routes are distinct graph-refinement algorithms derived from selected ideas in the named canonical algorithms, not feature-complete reimplementations. These package-owned routes are experimental and are excluded from the publication's principal comparative performance claims. Returned objects set implementation_status = "experimental" and experimental = TRUE. CUDA nndescent_style may resolve to direct external cuVS NN-descent. Native results report preferred_public_method, implementation_scope = "package_owned_style_implementation", a qualified implementation_label, and canonical_reimplementation = FALSE; print() also states that the route is experimental and package-owned. Direct cuVS NN-descent is labeled separately as an external-provider implementation. The Autotuning guide explains how the HPC target-recall sweeps convert speed, recall, failure, and shape-summary tables into deterministic C++ defaults for each method and backend.

Installation

Install the native FAISS dependency using the platform instructions below or the complete installation guide. Then install the R dependencies and faissR:

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")
BiocManager::install("Biobase")
install.packages("remotes")
remotes::install_github("tkcaccia/faissR")

FAISS is required and is not vendored. Rcpp >= 1.1.0 is required; update an older distribution-provided Rcpp before compiling. faissR compiles with C++20 because recent FAISS headers use C++20 syntax. On systems where FAISS is not visible through pkg-config or standard compiler paths, set FAISS_HOME, or provide separate include and library directories:

FAISS_HOME=/path/to/faiss R CMD INSTALL .
R CMD INSTALL --configure-vars='INCLUDE_DIR=/path/include LIB_DIR=/path/lib' .

On Debian/Ubuntu, also install libblas-dev and liblapack-dev alongside libfaiss-dev. R's bundled numerical libraries can omit the single-precision routines FAISS needs (for example ssyrk_). Linux configure now verifies both linking and loading and, when needed, adds a complete external LP64 BLAS/LAPACK provider. For custom installations use FAISSR_NUMERICAL_LIBS; see the Debian installation instructions. R's own BLAS/LAPACK libraries do not need to be replaced.

Linux, macOS, and Windows are eligible package platforms. Native Windows CPU builds require an Rtools-compatible FAISS library supplied through FAISS_HOME and complete single-precision BLAS/LAPACK routines. Windows configure verifies a compiled DLL can load and selects compatible Rtools numerical libraries where needed; FAISSR_NUMERICAL_LIBS allows explicit configuration. WSL2 remains the practical route for CUDA/cuVS. Automated Windows builders that do not provide FAISS compile a diagnostic build that loads and reports the missing system capability. Set FAISSR_REQUIRE_FAISS=1 when installation must fail unless a functional FAISS backend is linked. macOS binary builders should obtain a static FAISS library through the R-macos recipes system. configure detects recipe installations under /opt/R/<architecture> before generic system locations. Source builds can use any ABI-compatible prefix.

For repeatable native Windows/macOS and multi-distribution Linux checks, see the cross-platform test lab. Functional and diagnostic-only results are reported separately.

For a macOS source build outside the binary-builder environment, point FAISS_HOME and LIBOMP_HOME at compatible installations. An already-active conda/mamba environment is one possible local prefix:

conda install -c conda-forge faiss-cpu libomp
export FAISS_HOME="$CONDA_PREFIX"
export LIBOMP_HOME="$CONDA_PREFIX"
R CMD INSTALL .

configure detects CONDA_PREFIX passively when FAISS and libomp are already installed there. It never invokes a system package manager. This keeps Bioconductor and shared-machine builds explicit and reproducible.

Optional CUDA/cuVS builds are enabled only when requested or auto-detected:

CUDA_HOME=/path/to/cuda CUVS_HOME=/path/to/cuvs \
FAISSR_REQUIRE_CUDA=1 FAISSR_REQUIRE_CUVS=1 R CMD INSTALL .

See Installation for CRAN/source-build details. The installed package also includes an installation and portability vignette:

vignette("installation", package = "faissR")

A functional build is one for which faiss_available() is TRUE and a CPU smoke search completes. Diagnostic-only builds are used only on unsupported automated targets that lack FAISS; they load so that capability diagnostics can be inspected, but they do not provide nearest-neighbour computation.

Bioconductor Readiness

faissR includes the GPU biocViews term, a BiocStyle vignette, the R-standard License: MIT + file LICENSE identifier for the Expat License, and a top-level .BBSoptions file with GPU_reliance: optional. This opts the package into Bioconductor GPU builders without making NVIDIA libraries mandatory for the regular CPU/FAISS build. Local submission checks should be run from a source tarball:

R CMD build .
R CMD check --as-cran faissR_0.99.47.tar.gz

and then:

BiocCheck::BiocCheckGitClone(".")
BiocCheck::BiocCheck("faissR_0.99.47.tar.gz", `new-package` = TRUE)

FAISS is a required external system dependency. CUDA and cuVS are optional for CPU-only Bioconductor builds and must not be required there. NVIDIA GPU builds should use FAISSR_REQUIRE_CUDA=1 and, as needed, FAISSR_REQUIRE_CUVS=1 so missing GPU libraries fail during configuration. Maintainer Support Site registration and bioc-devel subscription are external submission steps.

On Debian/Ubuntu CPU builders, FAISS should be supplied by the FAISS development package, typically libfaiss-dev, together with libblas-dev and liblapack-dev. If a BiocStaging/r-universe log installs NVIDIA CUDA packages but not libfaiss-dev, the failing step is the automated system-requirements resolution: FAISS is mandatory for faissR, while CUDA/RAPIDS libraries are optional unless a GPU build is explicitly requested. Until the upstream r-universe resolver includes FAISS, the repository-level .prepare hook installs those three development packages for r-universe source builds and is excluded from the package tarball.

For macOS binary builds, the intended solution is a static FAISS dependency from the R-macos recipes system. Until that dependency is available on a given r-universe/BiocStaging worker, the configure script builds diagnostic stubs only for that explicitly recognized worker. backend_info() then reports FAISS as unavailable with reason runiverse_macos_diagnostic_stub_no_faiss. Functional Linux and macOS builds still require real FAISS.

For r-universe/WebAssembly, configure detects the wasm32-unknown-emscripten target and builds diagnostic stubs rather than using host /usr/include FAISS headers inside the Emscripten sysroot. The WASM artifact can report backend availability, but FAISS/CUDA/cuVS methods are not available because those native libraries are not webR system libraries. Supported Linux and macOS builds still require real FAISS.

FAISS GPU With cuVS

faissR distinguishes two GPU/cuVS routes [13-15]:

Use backend_info() and the attributes returned by nn() to confirm which route and parameters a result used. Use nn_capabilities() to inspect which public method, backend, and metric combinations are supported before launching a large benchmark.

GPU-Resident Output For Downstream Packages

Most R users should call nn(), which returns ordinary R matrices. Packages that already run CUDA code can instead call nn_gpu() when the KNN output must stay on the GPU:

res <- nn_gpu(
  x,
  k = 15,
  exclude_self = TRUE,
  method = "auto",
  metric = "euclidean",
  target_recall = 0.99
)

The returned faissR_gpu_knn object owns CUDA device buffers through an external pointer and reports:

The object is intentionally not converted to host memory by default. Use gpu_knn_to_host(res) only when explicitly inspecting or testing the result in R.

Other packages can also bypass the R wrapper and retrieve the C-callable entry point registered by faissR:

typedef SEXP (*faissR_nn_cuda_tuned_gpu_fun)(
  SEXP x,
  SEXP k,
  SEXP method,
  SEXP metric,
  SEXP include_self,
  SEXP target_recall
);

auto fn = reinterpret_cast<faissR_nn_cuda_tuned_gpu_fun>(
  R_GetCCallable("faissR", "faissR_nn_cuda_tuned_gpu_call")
);

The current GPU-resident route supports exact KNN for method = "auto", "exact", "flat", or "bruteforce". Euclidean inputs bfKnn when available and request similarities are converted on the CUDA device to faissR's shifted smaller-is-better distance. For 2D/3D Euclidean data, a direct-difference CUDA kernel avoids cancellation in the dot-product L2 identity. Cosine and correlation use native CUDA exact transforms with the same output contract. With method = "auto", nn_gpu() records the same compiled auto-selection metadata as nn(). If that policy would prefer an approximate method such as IVF for ordinary nn() but that provider cannot yet expose persistent GPU-resident result buffers, nn_gpu() keeps the exact-family GPU-resident route and records auto_preferred_backend, auto_preferred_method, and auto_residency_constraint. It also records the exact-family execution_tuning used by the GPU-resident route and the auto_preferred_tuning row for the preferred approximate CUDA method, available. Approximate FAISS GPU/cuVS methods still use nn() and return host objects until those provider result buffers are exposed as persistent GPU-resident objects.

cuVS NN-Descent Shared-Memory Note

On some RAPIDS cuVS builds, direct CUDA NN-descent can fail on high-dimensional FP32 L2 inputs with cudaErrorInvalidValue during cuvsNNDescentBuild. We traced this to the cuVS L2-norm kernel requesting more than CUDA's default dynamic shared memory per block without opting into the larger device-supported limit. A local cuVS patch that calls cudaFuncSetAttribute(cudaFuncAttributeMaxDynamicSharedMemorySize) before the kernel launch fixed the full COIL20 1440 x 16384 case and MNIST70k on the test machine. faissR does not vendor this cuVS patch or silently fall back to CPU; affected users should update to a cuVS release containing the fix or build cuVS with the patch. See the copy-ready upstream report in docs/cuvs-nndescent-shared-memory-issue.md.

Quick Example

library(faissR)

x <- scale(as.matrix(iris[, 1:4]))
nn_res <- nn(x, k = 15, backend = "cpu", metric = "euclidean", n_threads = 4)
nn_res$indices[1:3, 1:5]

License

faissR is distributed under the Expat License, a permissive, GPL-compatible free-software license. R package metadata identifies this standard license template as MIT + file LICENSE. FAISS also uses the Expat License, while cuVS uses Apache-2.0; both are supplied by the user's system and are not vendored. The CUDA toolkit and driver remain subject to NVIDIA's CUDA SDK license. See LICENSE.note and inst/THIRD_PARTY_LICENSES.md for links and the distribution boundary. External libraries such as FAISS and RAPIDS cuVS are linked as system dependencies and are not vendored into the R package [1-3,13-16].