Implementation

July 31, 2026 · View on GitHub

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

faissR is a standalone R package for nearest-neighbour search, kNN prediction, and k-means. FAISS is the required compiled vector-search dependency. CUDA and RAPIDS cuVS are optional compiled backends; the package must still install from source on CPU-only systems with FAISS installed [1-3,13-16]. The code does not call Python and does not require conda.

The implementation is organized around one public idea: users choose a device family with backend and an algorithm family with method. The package then resolves that request to a concrete compiled route, records the resolved backend in the result attributes, and fails early for unsupported combinations. This is important for reproducible benchmarking: backend = "cuda" must mean a CUDA route was attempted, not that the package silently ran a CPU fallback.

Design Rules

  1. FAISS CPU support is mandatory for the package build.
  2. CUDA, FAISS GPU, and cuVS support are optional at compile time.
  3. Explicit GPU backends fail clearly when GPU support is unavailable; they do not silently fall back to CPU.
  4. KNN outputs use simple 1-based indices and numeric distances matrices.
  5. Expensive KNN work should be reusable by embedding and supervised prediction functions.
  6. Benchmark-only quality helpers are kept out of the public API.
  7. External compiled libraries are linked as system dependencies rather than vendored into the package source tree.
  8. Distance semantics are explicit: algorithm choice belongs in method, while Euclidean/cosine/correlation/inner-product choices belong in metric.
  9. Approximate routes should expose enough metadata to make speed/quality trade-offs auditable.

Build And Runtime Model

configure detects FAISS first. A build without FAISS is considered misconfigured because FAISS is the core compiled dependency of faissR. On machines without CUDA, the package can still build with FAISS CPU and native CPU code. CUDA/cuVS support is enabled only when the required headers and libraries are detected.

At runtime, availability helpers (faiss_available(), faiss_gpu_available(), cuda_available(), and cuvs_available()) report compiled/runtime support. They are diagnostic helpers, not a substitute for execution-time validation. Every explicit backend request is checked again when the method runs. For example, backend = "cuda", method = "cagra" errors if neither FAISS GPU CAGRA nor direct cuVS CAGRA is available.

Compiled backends are reached through Rcpp/C++ bridge files:

  • FAISS CPU/GPU routines are isolated behind the FAISS bridge and use FAISS indexes such as Flat, IVF, IVFPQ, HNSW, NSG, NNDescent, and GPU CAGRA where the linked FAISS build exposes them [1-2,5-6,13-16].
  • Direct RAPIDS cuVS routines are isolated behind the cuVS bridge and are optional at build time [3].
  • CUDA-native helper kernels, such as the low-dimensional grid and candidate kernels, are isolated behind the CUDA bridge.

The package does not vendor FAISS, cuVS, CUDA, or cuDNN. Those projects remain system dependencies with their own release cadence and licenses.

nn()

nn() is the low-level nearest-neighbour function. It accepts a reference matrix/data frame and, optionally, a query matrix/data frame. If points is not supplied, it performs self-search. Results are faissR_nn objects containing neighbour indices, distances, the resolved backend, the metric, and exact versus approximate metadata.

Supported CPU routes include:

  • FAISS CPU Flat, IVF-Flat, IVF-PQ, IVFPQ FastScan, HNSW, NSG, and NN-Descent when present in the linked FAISS build [1-6,16,34];
  • faissR-native dense, NN-descent, Vamana, NSG-style, and candidate-refinement routines used where they are distinct from the FAISS indexes;
  • exact 2D/3D grid routes for low-dimensional Euclidean, cosine, and correlation self-KNN.

Supported CUDA routes include:

  • FAISS GPU Flat, IVF-Flat, IVF-PQ, and CAGRA when FAISS GPU support is built [1-2,13-16];
  • direct RAPIDS cuVS brute force, CAGRA, HNSW-from-CAGRA, IVF-Flat, IVF-PQ, IVFPQ FastScan 4-bit IVF-PQ, and NN-Descent when cuVS is available [3,13-15,22,34];
  • native CUDA 2D/3D grid search for low-dimensional Euclidean, cosine, and correlation self-KNN;
  • native CUDA candidate-refinement routes used by Vamana and NSG.

The validated high-performance metric is Euclidean/L2. Cosine and correlation are exposed for exact CPU, FAISS CPU/GPU Flat, FAISS CPU/GPU IVF-Flat, FAISS CPU/GPU IVFPQ, FAISS CPU FastScan, FAISS CPU HNSW, direct cuVS IVF/CAGRA/NN-Descent routes, and native graph-refinement paths. FAISS IP-capable approximate routes implement cosine by row L2 normalizing the inputs before inner-product search, and correlation by row centering plus L2 normalization before inner-product search; both routes return 1 - similarity distances. All-zero cosine rows and constant correlation rows are zero-normalized edge cases. faissR treats two zero-normalized rows as distance 0 and a zero-normalized row versus a nonzero row as distance 1. CPU FAISS Flat uses the exact CPU scorer for those rows to preserve deterministic small-k tie handling; explicit CUDA routes do not perform CPU repair and therefore error clearly for those degenerate normalized rows. CUDA results that record GPU residency are treated as final backend output: faissR does not re-sort rows, remove self-neighbours, or reshape include-self columns in R after the CUDA/FAISS/cuVS route returns. CUDA graph routes whose compiled path does not yet expose include-self shaping require exclude_self = TRUE and fail clearly otherwise. Direct cuVS IVF/PQ use normalized Euclidean search for cosine/correlation and the standard maximum-inner-product-to-L2 extra-dimension transform for raw inner product before building the L2 index. Direct cuVS CAGRA and NN-Descent use normalized Euclidean search for cosine/correlation. Raw inner product is supported only through routes with an implemented transform or native scorer: CAGRA uses a maximum-inner-product-to-L2 transform, while CUDA NN-descent marks raw inner product as unsupported because direct cuVS NN-descent does not expose that metric. Graph-style routes that implement cosine/correlation through normalized Euclidean search convert returned neighbour distances back to 1 - similarity with the stable formula normalized_euclidean_squared_over_2_to_1_minus_similarity. Those results record metric_transform and attr(result, "distance_transform"), and approximate routes also copy the fields into attr(result, "approximation"), so benchmark summaries can distinguish the search space from the reported distance semantics. The normalized cosine/correlation transform is cached in the R session as a row-major float32 matrix keyed by matrix contents, dimensions, and metric. FAISS and cuVS float-pointer adapters recognize this internal row-major payload and can pass it directly to the compiled index/search path on repeated calls. The bounded cache defaults to four transformed matrices and is controlled by options(faissR.cache_transformed_float32 = TRUE/FALSE) and options(faissR.cache_transformed_float32_max_entries = 4L). Inner-product search is exposed for exact native CPU scoring, FAISS Flat IP routes, FAISS IVF-Flat/IVFPQ IP, FAISS HNSW IP, native CPU graph-refinement routes, and supported CUDA/cuVS transforms. Approximate accelerator backends reject unsupported metric/backend combinations instead of returning neighbours computed under a different metric label.

Result Metadata

All KNN routes return a faissR_nn object with:

  • indices: an integer matrix of 1-based R row indices;
  • distances: a numeric matrix aligned with indices;
  • attr(result, "backend"): the public/resolved backend label returned to the user;
  • attr(result, "requested_backend"): the public backend argument supplied to nn()/nn(..., exclude_self = TRUE);
  • attr(result, "requested_method"): the public method argument after alias normalization;
  • attr(result, "resolved_backend"): when relevant, the concrete backend chosen behind an alias such as backend = "cuda";
  • attr(result, "tuning"): the normalized tuning policy used by the public wrapper;
  • attr(result, "metric"): the metric used;
  • attr(result, "exact"): whether the route is exact by construction;
  • attr(result, "approximation"): method-specific parameters for approximate routes, such as nlist, nprobe, HNSW M, CAGRA graph degree, or tuning metadata. Approximate selectors record deterministic no-pilot metadata such as tuning_policy, tuning_rule, and shape flags including high-dimensional, large-n, small-k, large-k, and non-Euclidean routing. Deterministic approximate-method parameter rules also record tuning_source = "cpp". For IVFPQ/PQ compression settings, PQ-specific fields are prefixed with pq_.
  • attr(result, "auto_selection"): for requests involving backend = "auto" or method = "auto", the compiled static shape/k/metric decision record. It stores policy = "cpp_static_shape_k_metric_selector", the predicted concrete backend, public method class, device class, the reason for the selection, explicit backend/method flags, backend/method decision reasons, n, p, query count, k, metric, work-size estimate, and slow_tuning = FALSE. This is a preflight record only; it does not run a pilot benchmark or build an index.

This metadata is intentionally simple because the same result object feeds graph construction, clustering, benchmarking, and supervised prediction.

Backend And Method Policy

The public KNN API separates device choice from algorithm choice:

  • backend = "auto" uses a validated CUDA route only when the requested method/metric combination is supported and CUDA/cuVS runtime support is available, and otherwise resolves to CPU;
  • backend = "cpu" forces CPU execution;
  • backend = "cuda" forces CUDA execution and fails clearly if unavailable.

The public method argument selects the algorithm. method = "auto" is the shape-aware selector for the chosen device. On CPU, it uses exact CPU for small work, exact grid search for large 2D/3D Euclidean/cosine/correlation self-KNN, FAISS IVF for million-row self-KNN where HNSW graph construction is too memory-heavy, FAISS HNSW for large high-dimensional self-KNN across all supported CPU metrics, FAISS Flat exact search for larger cosine/correlation/inner-product query or exact workloads, native CPU NSG-style candidate refinement for selected larger non-Euclidean self-KNN cases, and native CPU NN-descent for other large self-KNN cases, instead of exact brute force [1-2,5,21]. On CUDA, it uses CUDA grid search for large 2D/3D Euclidean/cosine/correlation self-KNN, then chooses between exact FAISS GPU Flat/cuVS brute force and IVF-Flat for Euclidean self-KNN from dataset shape, k, and target_recall. Non-self Euclidean queries stay on exact Flat/brute force. FAISS GPU Flat inner-product routes are used for small/query cosine, correlation, and raw inner-product searches when FAISS GPU Flat is available [13-15]. If CUDA/cuVS is present but FAISS GPU Flat is not, backend = "auto" keeps non-grid non-Euclidean searches on CPU instead of selecting an unavailable GPU index. The same rule applies when backend = "auto" is combined with an explicit method such as "flat" or "ivf": the selected method/metric must have a runtime-capable CUDA route, or auto uses the CPU route when that method/metric is supported on CPU. The public tuning argument controls method-specific pilot tuning. The default tuning = "auto" uses the recommended tuning policy for the resolved method; "cache", "pilot", and "fixed" can be selected explicitly, and "off"/"none" disables tuning. The route choice for method = "auto" and backend = "auto" is made by the C++ nn_auto_select_backend_cpp() selector. The R wrapper normalizes arguments, collects runtime capability flags and option thresholds, and dispatches to the compiled selector's backend. The deterministic parameter rules used by tuning = "auto" are also C++ owned: CPU Euclidean/cosine/correlation/inner-product exact query-batch and fitted-index reuse settings, CPU Euclidean/cosine/correlation/inner-product Flat query-batch and fitted-index reuse settings, CPU Euclidean/cosine/correlation/inner-product bruteforce query-batch and fitted-index reuse settings, CUDA Euclidean/cosine/correlation/inner-product exact FAISS GPU Flat query-batch and GPU resource-reuse settings, FAISS IVF, FAISS/PQ, FAISS HNSW, FAISS NSG, FAISS NN-descent, direct cuVS IVFPQ, cuVS CAGRA, cuVS NN-descent, native NSG, Vamana, native CUDA NN-descent, and related native graph-refinement parameters are computed by nn_tune_*_cpp() helpers. R still reads user-facing options(faissR.*) values, but clipping, default choice, requested-vs-effective values, tuning-rule labels, and shape flags are produced by the compiled policy layer. For Euclidean exact CUDA self-KNN, the selector distinguishes compact high-dimensional data from larger image-scale matrices. Compact shapes such as COIL20 and USPS use cuda_cuvs_bruteforce when cuVS is available, because validation showed the direct cuVS exact path was faster than FAISS GPU Flat while remaining an exact/high-recall route. Larger shapes such as FashionMNIST keep faiss_gpu_flat_l2 as the automatic exact route when FAISS GPU is available; cuda_cuvs_bruteforce remains available as an explicit method and as the fallback exact CUDA route when FAISS GPU Flat is unavailable.

Explicit methods map to the selected backend. For example, method = "grid", backend = "cpu" resolves to the CPU grid implementation, backend = "cuda" resolves to the CUDA grid implementation. All grid routes return the final include-self matrix layout from compiled code, avoiding R-side self-column reshaping. Invalid combinations fail before computation; for example, method = "cagra", backend = "cpu" errors because CAGRA is CUDA-only. For CUDA CAGRA, options(faissR.cagra_implementation = "auto") keeps the deterministic shape-aware provider rule: compact high-dimensional self-KNN uses direct cuVS CAGRA when both providers are available, while other shapes keep FAISS GPU CAGRA as the default when it is available; "faiss_gpu" or "cuvs" forces one provider for benchmark isolation. Runtime preflight and availability checks respect the forced provider for Euclidean, cosine, correlation, and inner-product CAGRA routes. Raw inner-product CAGRA uses a maximum-inner-product-to-L2 extra-dimension transform before graph search and returns faissR's shifted inner-product distances. Returned approximate NN objects record cagra_provider ("faiss_gpu" or "cuvs") and cagra_provider_option in attr(result, "approximation"), so benchmark tables can separate provider selection from the public method = "cagra" request. Direct RAPIDS cuVS CAGRA also records cagra_build_algo. The default "auto" is faissR's deterministic shape-aware build rule: compact high-dimensional self-KNN cases use cuVS iterative CAGRA construction, while other direct-cuVS CAGRA cases use the IVF-PQ graph builder. Explicit "ivf_pq", "nn_descent", and "iterative_cagra_search" requests are passed through as cuVS CAGRA graph-construction choices, not silent fallbacks to another public method. CUDA Euclidean CAGRA tuning = "auto" uses measured shape/k/target rows from benchmark_scripts/cuda_cagra_euclidean_shape_tuning_defaults_from_uploaded_results.csv. CUDA cosine CAGRA uses row-normalized float32 Euclidean graph search, while CUDA correlation CAGRA uses row-centered row-normalized float32 Euclidean graph search. Both non-Euclidean CAGRA auto-tuning tables are validation-pending seeds from the measured Euclidean CAGRA sweep; those rows record tuning_benchmark_target_met = FALSE until the corrected metric-specific sweeps are rerun. The correlation seed table is benchmark_scripts/cuda_cagra_correlation_shape_tuning_defaults_from_seeded_euclidean_results.csv. For compact high-dimensional small-k direct-cuVS CAGRA, the automatic graph degree floor is 32. This keeps the graph much smaller than the general 64-degree default while avoiding the low-recall row observed with a 16-degree graph on COIL20 correlation k = 5 and k = 10.

Direct cuVS CAGRA uses deterministic no-pilot defaults for tuning = "auto". If the user explicitly requests tuning = "cache" or tuning = "pilot", faissR runs recall tuning; if that pilot does not meet the configured recall target, the function stops and recommends FAISS GPU CAGRA or cuVS brute force rather than silently returning a poor graph-search result.

IVF probe defaults are conservative enough to avoid misleading speed-only results. CPU Euclidean, cosine, correlation, and raw inner-product IVF use compiled nlist/nprobe lookup tables from faissR_IVF_TUNING_CPU12_euclidean_20260630_161409, faissR_IVF_TUNING_CPU12_cosine_20260701_090337, and faissR_IVF_TUNING_CPU12_correlation_20260701_090337, and faissR_IVF_TUNING_CPU12_inner_product_20260701_090337, keyed by dataset shape, k bucket, and target_recall. Rows that reached the requested target across all datasets in a shape group record tuning_benchmark_target_met = TRUE; rows where IVF only had a partial best setting or a below-target best-recall setting record FALSE and keep a descriptive tuning_benchmark_basis. CPU Euclidean, cosine, correlation, and raw inner-product IVFPQ use compiled lookup tables from hpc_ivfpq_cpu12_euclidean_shape_defaults_20260630_161409, faissR_IVFPQ_TUNING_CPU12_cosine_20260701_090337, faissR_IVFPQ_TUNING_CPU12_correlation_20260701_090337, and faissR_IVFPQ_TUNING_CPU12_inner_product_20260701_090337. The tables control nlist, nprobe, pq_m, and pq_nbits by shape, k bucket, and target_recall. Cosine, correlation, and raw inner-product rows that did not reach 0.90, 0.95, or 0.99 in the HPC sweeps are deliberately labelled target_not_reached_best_available_* or best_available_partial_shape_datasets_* and return tuning_benchmark_target_met = FALSE. IVFPQ is treated as an explicit memory-pressure backend because product quantization can reduce recall substantially [6]. CPU IVFPQ requires at least 624 training rows so FAISS does not train underpopulated product-quantizer codebooks. Direct cuVS IVF-PQ uses its own deterministic small-training rule unless the user explicitly sets cuVS PQ bits. FAISS GPU IVFPQ remains an explicit route because GPU IVFPQ tuning is benchmarked separately from CPU IVFPQ. The public method = "ivfpq_fastscan" route is separate from general IVFPQ. On CPU it requires FAISS FastScan (IndexIVFPQFastScan) and uses 4-bit compressed PQ codes with optional Flat reranking. CPU raw inner-product FastScan uses FAISS FastScan IP. CPU cosine FastScan normalizes rows, runs FastScan L2 on the normalized matrix, and converts squared normalized L2 distances to 1 - cosine; CPU correlation subtracts each row mean before the same normalization/search/conversion route. The cosine, correlation, and inner-product auto-tuning seed rows are marked as validation pending until their corrected HPC sweeps are rerun. On CUDA it resolves to direct cuVS IVF-PQ with 4-bit compressed codes and records cuda_cuvs_ivfpq_fastscan; it does not fall back to CPU FastScan when CUDA/cuVS is unavailable. CUDA FastScan supports Euclidean/L2, cosine, and correlation; cosine row-normalizes to float32, while correlation row-centers and row-normalizes to float32 before cuVS L2 search and conversion back to the public distance. CUDA cosine and correlation auto-tuning are seeded from the CUDA Euclidean FastScan table until the corrected metric-specific sweeps are rerun [6,34]. Raw CUDA nn() calls reuse a fitted cuVS IVF-PQ external pointer, the dataset device buffer, and cuVS resources through a bounded session cache; repeated queries can therefore avoid retraining IVF centroids, rebuilding PQ codebooks/codes, and copying the training data to the GPU again. Self-query searches use the fitted dataset device buffer directly. For repeated prediction-style calls with a separate query matrix, the persistent cuVS handle can retain one query device buffer keyed by the query fingerprint, controlled by options(faissR.cache_cuda_ivfpq_query_buffers = TRUE), avoiding repeated query float conversion and host-to-device upload on cache hits. CUDA FastScan searches are submitted as large query batches controlled by FAISSR_CUVS_IVF_BATCH_SIZE (default 32768); for multi-query calls, faissR clamps invalid tiny values so the cuVS search loop does not become row-by-row. cuVS requires pq_bits * pq_dim to be byte aligned for IVF-PQ; faissR validates and repairs invalid CUDA IVFPQ FastScan 4-bit requests before build, preserving both requested and actual PQ parameters in result metadata rather than skipping a dataset. The HPC IVFPQ FastScan tuning scripts sweep nlist, nprobe, and byte-aligned CUDA pq_dim values in addition to optional CUDA batch size. Smaller pq_dim and smaller nprobe are expected to be faster but can reduce recall, while nlist controls the IVF build/search balance. For CPU Euclidean, cosine, correlation, and raw inner-product FastScan, the default tuning = "auto" path is resolved in C++ from the uploaded CPU12 sweep faissR_IVFPQ_FASTSCAN_TUNING_CPU12_euclidean_20260630_161409; cosine, correlation, and inner product use seeded metric-specific rows until the corresponding corrected HPC sweeps are rerun. The compiled table uses n, p, k, and target_recall to select nlist, nprobe, pq_m, 4-bit PQ, refine_factor, and FastScan block size. It uses separate small-data buckets for high-dimensional and lower-dimensional matrices so that COIL20-like shapes do not inherit USPS-like parameters. Best-available rows that did not reach the requested target are exposed through tuning_benchmark_target_met = FALSE rather than being reported as verified accuracy defaults. FAISS HNSW uses no pilot tuning in the user call. Its default parameters are a static shape/k/metric policy implemented in C++ and selected by target_recall. The public options are target_recall = 0.9, 0.95, and 0.99; lower targets reduce HNSW graph/search effort for speed. Euclidean, cosine, correlation, and raw inner-product CPU FAISS HNSW use the CPU12 HPC sweeps from faissR_HNSW_TUNING_CPU12_euclidean_20260630_161409 and faissR_HNSW_TUNING_CPU12_cosine_20260701_082849, and faissR_HNSW_TUNING_CPU12_correlation_20260701_090337, and faissR_HNSW_TUNING_CPU12_inner_product_20260701_090337: shape groups are small-n, medium low-dimensional, large low-dimensional, and large high-dimensional; k buckets are 15, 30, 50, and 100; and each compiled row is the fastest concrete HNSW setting available for its requested target. Rows that did not meet the target on every dataset in the shape group report tuning_benchmark_target_met = FALSE; this is common for raw inner-product large-shape and 0.99 rows. Other shapes keep the balanced general HNSW defaults when no benchmark-derived row is available.

Method Families

The public method names are stable user-facing labels. Each algorithm family has one public method name; implementation labels such as faiss_hnsw, faiss_ivf, faiss_gpu_ivf_flat, or cuda_cuvs_cagra are reserved for resolved backend metadata and internal benchmark diagnostics. Internally, the public method names map to different concrete functions depending on backend.

MethodCPU behaviorCUDA behaviorNotes
autoShape-aware exact/grid/FAISS IVF/FAISS HNSW selector.Shape-aware CUDA grid, then Euclidean Flat-vs-IVF selection by shape, k, and target_recall; non-Euclidean auto uses exact FAISS GPU Flat or validated graph routes where available. Explicit CUDA exact/brute-force calls can use transformed cuVS brute force.Default for general use.
exactFAISS Flat L2 for Euclidean, normalized FAISS Flat cosine for cosine, centered/normalized FAISS Flat correlation for correlation, and FAISS Flat IP for raw inner product.FAISS GPU Flat when available; otherwise cuVS brute force can provide exact transformed metric search.Accuracy-first baseline. CPU Euclidean/cosine/correlation/inner-product exact uses compiled metric/shape/k/target policies for FAISS query batching and fitted-index reuse, while CUDA Euclidean/cosine/correlation/inner-product exact uses metric-specific FAISS GPU Flat query-batch policies. CUDA inner-product exact currently uses benchmark_scripts/cuda_exact_inner_product_shape_tuning_defaults_from_seeded_euclidean_results.csv, seeded from measured Euclidean CUDA exact rows until the dedicated sweep replaces it; recall remains exact by construction.
flatFAISS Flat L2/IP index; cosine and correlation use normalized Flat IP.FAISS GPU Flat L2/IP; cosine and correlation use normalized Flat IP; degenerate zero-normalized rows error instead of being repaired on CPU.Exact FAISS route [1-2,16]. CPU Euclidean/cosine/correlation/inner-product Flat uses compiled metric/shape/k/target policies for FAISS query batching and fitted Flat-index reuse, while CUDA Euclidean/cosine/correlation/inner-product Flat uses compiled FAISS GPU Flat query-batch policies. CUDA Flat inner product currently uses benchmark_scripts/cuda_flat_inner_product_shape_tuning_defaults_from_seeded_euclidean_results.csv, seeded from measured CUDA Flat Euclidean rows until the dedicated sweep replaces it; rows are stored in attr(result, "flat_tuning"), and recall remains exact by construction.
bruteforceFAISS Flat L2 for Euclidean, normalized FAISS Flat cosine for cosine, centered/normalized FAISS Flat correlation for correlation, and FAISS Flat IP for raw inner product.Direct cuVS brute force when available, with exact transforms for cosine, correlation, and raw inner product.CPU Euclidean/cosine/correlation/inner-product brute force uses compiled metric/shape/k/target policies for FAISS query batching and fitted Flat-index reuse, stored in attr(result, "bruteforce_tuning"), while recall remains exact by construction. CUDA Euclidean/cosine/correlation/inner-product bruteforce stores cuVS query-batch/resource policies; correlation uses benchmark_scripts/cuda_bruteforce_correlation_shape_tuning_defaults_from_proxy_results.csv and raw inner product uses benchmark_scripts/cuda_bruteforce_inner_product_shape_tuning_defaults_from_seeded_euclidean_results.csv until the corrected metric-specific sweeps replace the proxy/seed rows. Useful for comparing direct cuVS against FAISS GPU Flat [1-3,16].
gridNative 2D/3D exact spatial grid.CUDA 2D/3D grid.Errors outside two or three columns.
hnswFAISS CPU HNSW.RAPIDS cuVS HNSW from CAGRA.CPU Euclidean/cosine/correlation/inner-product graph-search routes use compiled shape/k/target tiers selected by target_recall = 0.9, 0.95, or 0.99, and record whether the benchmark target was met. Raw inner-product HNSW rows that remained below target are exposed with tuning_benchmark_target_met = FALSE. CUDA builds a cuVS CAGRA seed graph and converts it with cuvsHnswFromCagraWithDataset using the host dataset and cuVS CPU hierarchy; Euclidean, cosine, correlation, and raw inner product use compiled CUDA shape/k/target tables, with cosine implemented through normalized float32 Euclidean graph search, correlation through centered normalized float32 Euclidean graph search, and raw inner product through a maximum-inner-product-to-L2 transform. CUDA raw inner product currently uses benchmark_scripts/cuda_hnsw_inner_product_shape_tuning_defaults_from_seeded_euclidean_results.csv, seeded from measured Euclidean CUDA HNSW rows until the dedicated IP sweep replaces it. Result metadata records cuda_hnsw_design = "cuvs_hnsw_from_cagra_cpu_hierarchy" because this is not a pure all-GPU HNSW implementation [5,16,22-23].
ivfFAISS CPU IVF-Flat L2/IP; cosine and correlation use normalized IVF IP.FAISS GPU IVF-Flat L2/IP; cosine and correlation use normalized IVF IP.Coarse-list approximate route [1-2,16]. CPU Euclidean, cosine, correlation, and raw inner-product auto tuning use compiled shape/k/target nlist/nprobe tiers. CUDA Euclidean, cosine, and correlation IVF also use compiled shape/k/target nlist/nprobe tiers from GPU sweeps; raw inner product uses FAISS GPU IVF IP with a validation-pending seed table from the CUDA Euclidean IVF sweep. Metadata records whether the benchmark target was actually met.
ivfpqFAISS CPU IVF-PQ L2/IP; cosine and correlation use normalized IVFPQ IP.FAISS GPU IVF-PQ L2/IP; cosine and correlation use normalized IVFPQ IP.Compressed approximate route [6,16]. CPU Euclidean, cosine, correlation, and raw-inner-product plus CUDA Euclidean, correlation, and raw-inner-product auto tuning use compiled shape/k/target rows for both IVF and PQ settings, and record whether the benchmark target was actually met. CUDA raw inner product uses cuda_ivfpq_inner_product_shape_tuning_defaults_from_seeded_euclidean_results.csv until the dedicated metric sweep replaces the validation-pending rows.
ivfpq_fastscanFAISS CPU IndexIVFPQFastScan with 4-bit PQ and optional Flat refinement; raw inner product uses FastScan IP, while cosine and correlation are transformed to normalized L2 search before FastScan.Direct RAPIDS cuVS IVF-PQ with 4-bit compressed codes and session-local fitted-index/resource reuse; cosine uses row-normalized float32 L2 search, correlation uses centered row-normalized float32 L2 search, and raw inner product uses the maximum-inner-product-to-L2 extra-dimension transform before distance conversion.IVFPQ FastScan compressed-code route; CPU and CUDA support Euclidean/L2, raw inner product, and transformed cosine/correlation. CPU requires linked FAISS FastScan support and CUDA requires cuVS, with no CPU fallback for explicit CUDA requests [6,34].
vamanaNative DiskANN/Vamana-style robust-pruned candidate graph with CPU refinement.Native DiskANN/Vamana-style robust-pruned candidate graph with CUDA row-candidate refinement.Distinct pruned directed graph route implemented in faissR; large high-dimensional CPU inputs use deterministic HNSW seed neighbours before robust pruning, while smaller CPU inputs keep exact seed neighbours. CPU Euclidean/cosine/correlation/inner-product and CUDA Euclidean/cosine/correlation/inner-product auto tuning use compiled shape/k/target rows for Vamana r, search_l, and alpha; CUDA Euclidean/cosine rows are measured and CUDA correlation/raw-inner-product rows are validation-pending defaults seeded from measured CUDA cosine tuning until the dedicated metric sweeps are rerun. Robust pruning protects the first k seed neighbours before applying the Vamana rule; cuVS Vamana currently provides build/serialization rather than KNN search [3,5,24].
nsgNative CPU NSG-style self-KNN candidate graph for Euclidean, cosine, correlation, and inner product.Native CUDA NSG-style self-KNN candidate graph for all public metrics.Optional graph-search baseline; public CPU NSG avoids unsafe linked-FAISS graph construction by using faissR-owned candidate pruning/refinement. CPU Euclidean/cosine/correlation/inner-product and CUDA Euclidean/cosine/correlation/inner-product auto tuning use compiled shape/k/target rows for NSG pruning degree r and seed/candidate graph width graph_k; CUDA Euclidean/cosine rows are measured and CUDA correlation/raw-inner-product rows are validation-pending defaults seeded from measured CUDA cosine tuning until the dedicated metric sweeps are rerun. Large high-dimensional CPU inputs use deterministic HNSW seed neighbours before NSG/MRNG-style pruning; smaller CPU inputs and CUDA keep exact seed neighbours. Native CPU/CUDA NSG protect the first k seed neighbours before pruning and use backend-specific auto defaults/options (faissR.cpu_nsg_*, faissR.cuda_nsg_*) [5,16,21,29].
nndescentNative CPU NNDescent for Euclidean/L2, cosine, correlation, and raw inner product.Direct cuVS NN-descent for Euclidean/L2, cosine, and correlation; raw inner product is unsupported.Approximate KNN graph construction; cosine/correlation use normalized Euclidean search and CPU raw inner product ranks larger dot products through shifted smaller-is-better distances. CPU Euclidean/cosine/correlation/inner-product auto tuning uses compiled shape/k/target rows for candidate pool size, iteration count, candidate breadth, and random-projection seeding. CUDA Euclidean auto tuning uses measured cuVS sweep rows; CUDA cosine and correlation use transformed float32 search and seeded policies derived from the CUDA Euclidean table until corrected metric-specific sweeps are rerun. CUDA raw inner product is rejected because cuVS NN-descent accepts one symmetric L2 graph dataset and cannot use the asymmetric maximum-inner-product transform; FAISS NNDescent is disabled by default because linked FAISS builds can abort during graph construction [3-4,16].
cagraUnsupported.FAISS GPU CAGRA or direct cuVS CAGRA; faissR.cagra_implementation can force "faiss_gpu" or "cuvs", while "auto" uses a deterministic shape-aware provider rule. Cosine/correlation use normalized Euclidean graph search; raw inner product uses a maximum-inner-product-to-L2 transform. Euclidean auto tuning uses measured CUDA CAGRA rows, while cosine, correlation, and raw inner product use validation-pending tables seeded from those rows until corrected metric-specific sweeps are rerun. Raw inner-product defaults are stored in benchmark_scripts/cuda_cagra_inner_product_shape_tuning_defaults_from_seeded_euclidean_results.csv.CUDA-only FAISS/cuVS graph-search method [3,13-16].

For direct cuVS brute force, cosine and correlation are transformed to exact Euclidean search by row normalization, and raw inner product uses the exact maximum-inner-product-to-L2 transform before calling the cuVS L2 kernel.

Unsupported method/backend pairs stop before computation. This makes benchmark failures interpretable: a row marked unavailable or unsupported means the requested algorithm/device combination does not exist in the package, not that a different algorithm was substituted.

For native CPU NN-descent with metric = "euclidean", metric = "cosine", or metric = "correlation" and tuning = "auto", the selector is resolved in C++ from metric-specific CPU tables such as faissR_NNDESCENT_TUNING_CPU12_euclidean_20260630_161409, faissR_NNDESCENT_TUNING_CPU12_cosine_20260701_090337, and faissR_NNDESCENT_TUNING_CPU12_correlation_20260701_090337. CUDA NN-descent uses the measured Euclidean table and seeded cosine/correlation policies derived from it until corrected metric-specific CUDA sweeps are available. Cosine search is performed as normalized Euclidean NN-descent; correlation is performed as centered normalized Euclidean NN-descent. The table is keyed by n, p, k, and target recall tier and returns pool_size, n_iters, max_candidates, and n_random_projections. Metadata records the selected policy, shape group, k bucket, target-recall code, source sweep, tuning metric, and whether that row actually reached the requested recall tier across the shape group. CUDA NN-descent uses the same C++ selector shape/k/target interface: Euclidean is backed by measured cuVS rows, while cosine is currently seeded from those Euclidean rows and marked as validation-pending in the returned metadata.

For native CPU NSG with metric = "euclidean", "cosine", or "correlation" and tuning = "auto", the selector is resolved in C++ from faissR_NSG_TUNING_CPU12_euclidean_20260630_161409, faissR_NSG_TUNING_CPU12_cosine_20260701_090337, or faissR_NSG_TUNING_CPU12_correlation_20260701_090337. Cosine search is performed as normalized Euclidean NSG refinement and correlation as row-centered, normalized Euclidean NSG refinement, but the tuning helper receives the original metric and uses a metric-specific table. The table is keyed by n, p, k, and target recall tier and returns the NSG pruning degree r and seed/candidate graph width graph_k. The compiled rows cover small-n, medium low-dimensional, large low-dimensional, and large high-dimensional shape groups. Metadata records the source sweep, tuning metric, and tuning_benchmark_target_met; large low-dimensional rows are marked as best-available partial-shape rows when the uploaded sweep did not complete the same setting for every dataset in that shape group.

CUDA NSG uses the same C++ selector interface. For Euclidean and cosine tuning = "auto", faissR uses measured CUDA shape/k/target rows from faissR_NSG_TUNING_CUDA_euclidean_20260702_013830 and faissR_NSG_TUNING_CUDA_cosine_20260702_211910. CUDA cosine row-normalizes the float32 input before native CUDA NSG candidate refinement and converts normalized Euclidean distances back to cosine distance. CUDA correlation row-centers and row-normalizes float32 input before the same native CUDA NSG candidate-refinement kernel; until the dedicated CUDA correlation sweep is available, tuning = "auto" seeds r and graph_k from the measured CUDA cosine table in benchmark_scripts/cuda_nsg_correlation_shape_tuning_defaults_from_seeded_cosine_results.csv. CUDA raw inner product uses the native CUDA NSG shifted dot-product ordering and, until the dedicated CUDA inner-product sweep is available, seeds r and graph_k from the same measured CUDA cosine table in benchmark_scripts/cuda_nsg_inner_product_shape_tuning_defaults_from_seeded_cosine_results.csv. The returned metadata records tuning_metric = "correlation", tuning_benchmark_source = "hpc_nsg_cuda_correlation_validation_pending_seeded_from_cosine_20260702_211910", or tuning_metric = "inner_product" with tuning_benchmark_source = "hpc_nsg_cuda_inner_product_validation_pending_seeded_from_cosine_20260702_211910", and tuning_benchmark_target_met = FALSE for those seeded rows.

For native CPU Vamana with metric = "euclidean", metric = "cosine", or metric = "correlation" and tuning = "auto", the selector is resolved in C++ from faissR_VAMANA_TUNING_CPU12_euclidean_20260630_161409, faissR_VAMANA_TUNING_CPU12_cosine_20260701_090337, or faissR_VAMANA_TUNING_CPU12_correlation_20260701_090337. Cosine search is performed as normalized Euclidean Vamana refinement, and correlation search is performed as centered, normalized Euclidean Vamana refinement, but the tuning helper receives the original metric and uses a metric-specific table. The table is keyed by n, p, k, and target recall tier and returns Vamana r, search breadth search_l, and robust-pruning alpha. The compiled rows cover small-n, medium low-dimensional, large low-dimensional, and large high-dimensional shape groups. Metadata records the source sweep, tuning metric, and tuning_benchmark_target_met; large low-dimensional rows are marked as best-available partial-shape rows when the uploaded sweep did not complete the same setting for every dataset in that shape group.

CUDA Vamana uses the same C++ selector interface. For Euclidean and cosine tuning = "auto", faissR uses measured CUDA shape/k/target rows from faissR_VAMANA_TUNING_CUDA_euclidean_20260702_042943 and faissR_VAMANA_TUNING_CUDA_cosine_20260702_232209. CUDA cosine row-normalizes the float32 input before native CUDA Vamana candidate refinement and converts normalized Euclidean distances back to cosine distance. CUDA correlation row-centers and row-normalizes float32 input before the same native CUDA Vamana candidate-refinement kernel; until the dedicated CUDA correlation sweep is available, tuning = "auto" seeds r, search_l, and alpha from the measured CUDA cosine table in benchmark_scripts/cuda_vamana_correlation_shape_tuning_defaults_from_seeded_cosine_results.csv. CUDA raw inner product uses the native CUDA Vamana shifted dot-product ordering and, until the dedicated CUDA inner-product sweep is available, seeds r, search_l, and alpha from the same measured CUDA cosine table in benchmark_scripts/cuda_vamana_inner_product_shape_tuning_defaults_from_seeded_cosine_results.csv. The returned metadata records tuning_metric = "correlation", tuning_benchmark_source = "hpc_vamana_cuda_correlation_validation_pending_seeded_from_cosine_20260702_232209", or tuning_metric = "inner_product" with tuning_benchmark_source = "hpc_vamana_cuda_inner_product_validation_pending_seeded_from_cosine_20260702_232209", and tuning_benchmark_target_met = FALSE for those seeded rows.

Automatic Tuning

The public tuning argument controls method-specific tuning for approximate GPU routes. Its default, tuning = "auto", means “use the appropriate default policy for the resolved method.” Current policies are intentionally conservative:

  • auto: use deterministic no-pilot defaults for the resolved method;
  • cache: run a pilot tuning step when needed and reuse/store the selected parameters;
  • pilot: run the pilot for this call without persisting the result;
  • fixed: use fixed defaults but still record tuning metadata;
  • off/none: disable tuning.

When explicitly requested, FAISS GPU IVF tuning tests candidate nlist/nprobe settings on a sample and selects the fastest candidate that meets the recall target when possible. Explicit cuVS CAGRA tuning tests graph/search parameters and rejects the route if the pilot cannot meet the configured minimum recall. This opt-in behavior exists because some direct cuVS CAGRA runs were fast but produced untrustworthy recall on raw high-dimensional data.

nn(..., exclude_self = TRUE)

nn(..., exclude_self = TRUE) wraps self-KNN and returns exactly k non-self neighbours. It is used internally by approximate-search routes and benchmarks. The function requests enough neighbours to remove the self-match safely and keeps the same faissR_nn result shape as nn(). For CUDA routes, self-neighbour removal is requested from the compiled FAISS, cuVS, or native CUDA implementation. The R wrapper does not drop self columns or repair final CUDA KNN matrices after the backend returns.

candidate_knn()

candidate_knn() ranks a supplied candidate set for each query row. It avoids a full all-pairs search when another algorithm has already proposed likely neighbours. CPU candidate ranking is exact for all public metrics. CUDA candidate ranking is used only when compiled and explicitly requested; it scores Euclidean candidates directly, cosine/correlation candidates after the same row-normalized Euclidean transform used by graph-search methods, and raw inner-product candidates through a dedicated CUDA kernel mode. CUDA and CPU inner-product candidate scoring both return shifted smaller-is-better distances where the best dot product in each query row has distance 0. The output can feed prediction, embedding, or diagnostic code.

fast_kmeans()

fast_kmeans() provides CPU and CUDA k-means routes. CPU builds use compiled FAISS/native numeric paths. CUDA builds try FAISS GPU k-means and direct cuVS k-means when those libraries are available [7-8]. Results include cluster assignments, centres, within-cluster sums of squares, cluster sizes, iteration count, selected backend, and run parameters. The top-level backend field is the implementation that actually ran, while parameters$requested_backend and parameters$resolved_backend preserve the public backend request and resolved device policy.

With tuning = "auto", omitted max_iter, n_init, and tol values are chosen by deterministic shape rules based on nrow(data), ncol(data), and the requested number of centres. The function does not run pilot k-means jobs or benchmark candidate parameter sets inside a user call. This keeps runtime predictable while allowing small problems to use more restarts and large high-dimensional problems to use cheaper convergence settings. The selected policy is recorded in result$parameters$tuning; its shape metadata includes estimated work, n_per_center, and flags such as many_centers and small_many_centers/few_points_many_centers. The stable rule label groups comparable tuning tiers, while rule_detail preserves the exact n, p, centers, n_per_center, and work estimate that produced the rule. Small many-cluster problems can keep 100 Lloyd iterations and use three restarts when the job is cheap enough; this includes both well-populated clusters and few-points-per-center jobs where a small multistart budget improves stability without running pilot searches. Genuinely large or high-dimensional problems keep cheaper settings. For benchmark-like shapes, MNIST70k with 10 centres uses the medium_single_start rule with 75 iterations and one restart, million-row low-dimensional data uses large_fast_convergence with 50 iterations, one restart, and tol = 1e-3, while a small 50,000 x 10 / 100-cluster job uses small_many_centers_multistart with 100 iterations and three restarts. These are fixed rules, not pilot benchmark loops. When centers = 1, fast_kmeans() uses the exact column mean for every backend request and records single_cluster_exact_mean, because iterative k-means cannot improve that solution on any backend. When centers = nrow(data), fast_kmeans() uses the exact singleton assignment for every backend request and records singleton_exact_identity, because every observation is already its own optimal cluster. Explicit CUDA requests for these exact cases return the trivial result and record parameters$resolved_backend = "trivial" rather than launching GPU work. The resolved_from field records whether max_iter, n_init, and tol were selected by auto/default rules or supplied explicitly. result$parameters$tuning$effective records the final values used after explicit overrides and "auto" defaults have been resolved, so benchmark code can summarize the effective k-means run without comparing multiple parameter fields. The flat aliases effective_max_iter, effective_n_init, and effective_tol expose the same values for simple CSV summaries.

The public backend policy follows the KNN device contract but adds a k-means-specific shape gate: backend = "auto" uses CUDA only when CUDA plus FAISS GPU k-means or direct cuVS k-means is compiled and available and the estimated work is large enough to justify GPU launch and host/device copy overhead. Small jobs resolve to CPU even on CUDA-capable machines; backend = "cpu" forces the CPU route; backend = "cuda" requires an accelerated route and errors if unavailable. This makes k-means behavior consistent with nn(). The auto max_iter/n_init/tol rule is implemented by kmeans_auto_params_cpp(), and the CUDA/CPU gate is implemented by kmeans_auto_select_backend_cpp() using the compiled policy from kmeans_auto_backend_policy_cpp(). The R layer only normalizes public arguments, reads documented option thresholds, and passes runtime availability flags before calling these compiled helpers. The gate is deterministic and recorded in result$parameters$tuning$backend_policy, with a reason such as "small_cpu_preferred", "few_points_per_center_cpu_preferred", "work_at_least_1e8", "input_at_least_256MiB", "large_high_dimensional_input", "single_cluster_exact_mean", or "singleton_exact_identity" so benchmark summaries can audit why backend = "auto" selected CPU or CUDA without running extra tuning jobs. For the size gate, the policy records both nbytes (ordinary R double input footprint) and gpu_transfer_nbytes (float32 data passed to FAISS/cuVS), and the CUDA threshold is applied to gpu_transfer_nbytes. Benchmark-derived threshold refinements can be applied without changing package code by setting options(faissR.kmeans_cuda_work_threshold = ...), options(faissR.kmeans_cuda_nbytes_threshold = ...), options(faissR.kmeans_cuda_large_n_threshold = ...), or options(faissR.kmeans_cuda_large_p_threshold = ...), or options(faissR.kmeans_cuda_min_n_per_center = ...); invalid option values fall back to the documented defaults. result$parameters$tuning$selection is the compact no-pilot audit record: it stores the requested and predicted backends, runtime capability flags, shape/work estimates, effective max_iter, n_init, and tol, explicit_backend, backend_decision, and slow_tuning = FALSE. The backend_decision field is the shape-policy reason for auto requests and explicit_cpu/explicit_cuda for explicit device requests, so benchmark summaries do not confuse a forced backend with an automatic choice. The returned object also records hit_max_iter and converged, derived from the effective iteration cap and the backend-reported iteration count. These flags are conservative convergence diagnostics for benchmark tuning: they do not run extra checks, but they make repeated under-iteration visible in cycle summaries. seed controls CPU/statistics and FAISS k-means paths. The direct cuVS C API path currently does not expose an explicit seed in the stable params structure, so repeated direct-cuVS runs use backend-controlled initialization and benchmark cycle summaries should rely on the observed ARI/withinss stability columns.

knn() And predict()

knn() is the high-level supervised kNN API. It uses one public model/predict interface for CPU, FAISS, CUDA, and cuVS-backed neighbour search.

Two forms are supported:

model <- knn(Xtrain, Ytrain)
pred <- predict(model, Xtest)

and immediate prediction:

pred <- knn(Xtrain, Ytrain, Xtest)
prob <- knn(Xtrain, Ytrain, Xtest, type = "prob")

The fitted model stores the training matrix, response, task type, backend, method, metric, tuning policy, k, and thread settings. predict() performs classification or regression from neighbour votes. predict(type = "prob") returns class probabilities for classification. Prediction outputs carry attr(result, "faissR_nn") metadata from the underlying nn() call, including requested backend/method/tuning, resolved backend, metric, k, and whether the route was exact.

For explicit CPU Euclidean method = "hnsw" models, knn() builds the FAISS HNSW index during fitting and keeps it as a session-local external pointer. The FAISS HNSW pointer owns the IndexHNSWFlat object; FAISS copies vectors into the index during add(), so a separate training buffer is not retained. Matching predict() calls search the fitted index directly and record approximation$index_reused = TRUE; mismatched calls or invalid external pointers rebuild the same route through nn().

The supervised API intentionally reuses nn() rather than creating independent FAISS/cuVS model classes. That keeps method selection, backend validation, tuning, and result semantics identical between unsupervised KNN and supervised kNN prediction.

Availability Helpers

  • faiss_available() reports whether FAISS support was compiled and linked.
  • faiss_gpu_available() reports whether the linked FAISS build reports GPU support.
  • cuda_available() reports CUDA build/runtime availability.
  • cuvs_available() reports direct RAPIDS cuVS availability.
  • backend_info() returns a data frame summarizing compiled/runtime backends, public call hints, non-public implementation route labels, devices, and notes.

These helpers are informational; explicit backend calls still validate availability at execution time.

Memory And Data Representation

R stores ordinary numeric matrices as double precision. FAISS and cuVS commonly operate on float data internally. Most accelerated routes therefore need at least one conversion/copy from R's column-major double matrix to backend-friendly buffers. For moderate datasets this overhead is small relative to KNN search; for very large datasets it can dominate memory use.

The core FAISS KNN implementation is being moved toward float-pointer entry points. The first direct public slice accepts optional float::fl()/float32 matrices in nn() for the CPU FAISS Flat route across the four public metrics: Euclidean, cosine, correlation, and inner product. Cosine and correlation are normalized in the float32 row-major buffer before FAISS IndexFlatIP search, with the same zero-row semantics as the double-precision routes. CPU FAISS HNSW also accepts the same float32 matrix adapter, so the CPU HNSW route avoids the previous float32-to-R-double-to-float32 path. Float objects are read from their float32 payload and copied only once into each backend's row-major float* layout. Ordinary R double matrices still work and are converted once to float32 internally. When an ordinary R double input uses output = "float", direct Euclidean FAISS/cuVS routes enter float-pointer adapters instead of producing an intermediate R double distance matrix. This includes CPU FAISS Flat/IVF/IVFPQ/FastScan, cached CPU FAISS fitted indexes, FAISS GPU Flat/IVF/IVFPQ, direct Euclidean RAPIDS cuVS brute-force/CAGRA/IVF/IVFPQ routes, and CUDA IVFPQ FastScan for Euclidean plus transformed cosine and correlation routes. CUDA HNSW uses the direct cuVS HNSW wrapper path from a CAGRA seed graph and records that design in result metadata. FAISS CPU/GPU and RAPIDS cuVS nearest-neighbour routes can also receive float::fl() inputs through direct C++ adapters. Native routes that still expose only NumericMatrix inputs now error for float32 inputs instead of silently converting benchmark data back to R double. This keeps benchmark timings honest: input_layout and float32_compatibility_conversion show whether the route consumed a supplied float32 payload directly or performed a one-time double-to-float adapter conversion, and unsupported native float32 routes must be implemented separately before they can be included in a float32-only benchmark.

The float32 adapter records the route it used in the returned KNN object. input_layout distinguishes ordinary R double conversion, float32 payload transpose, mixed reference/query adapters, direct row-compatible float32 payloads used for one-row or one-column float::fl() matrices. input_owns_data records whether FAISS consumed an owned adapter buffer. For cosine and correlation, direct float32 payloads still make one owned copy before normalization because those transforms are applied in-place before FAISS search.

Distance output remains an ordinary R numeric matrix by default. Calling nn(..., output = "float") or nn(..., exclude_self = TRUE, output = "float") stores distances as a float::fl()/float32 object and records both distance_type = "float32" and attr(result, "distance_type") = "float32". On direct float32 FAISS/cuVS routes, faissR constructs the returned float32 distance payload directly from backend float results, so Euclidean Flat/IVF/IVFPQ/FastScan and direct Euclidean CUDA/cuVS routes avoid an intermediate R double distance matrix. Transformed metrics and deterministic zero-row correction for cosine/correlation can still repair or convert a double distance matrix before returning float32 because those paths need R-side metric post-processing.

For downstream CUDA packages that need to avoid the final device-to-host copy, nn_gpu() returns a separate GPU-resident object rather than overloading nn(..., output = "float"). The returned faissR_gpu_knn list owns a CUDA result handle and exposes non-owning indices_ptr and distances_ptr external pointers. The buffers are column-major with shape n_query x k, indices_type = "int32", distance_type = "float32", and index_base = 1L. The owning handle must be kept alive by downstream code for as long as either device pointer is used. gpu_knn_to_host() is an explicit diagnostic helper that copies those buffers back to R matrices; it is never called automatically by nn_gpu().

The first GPU-resident implementation covers exact method labels method = "auto", "exact", "flat", and "bruteforce". Euclidean inputs with more than three columns and raw inner-product search use FAISS GPU direct bfKnn when available and write FAISS row-major, zero-based results into transient device buffers; a small CUDA post-processing kernel removes the self-neighbour when requested, converts indices to faissR's 1-based int32 format, takes the square root of L2 distances, or converts inner-product similarities to row-best shifted distances, and stores the final result in the existing column-major faissR_gpu_knn layout. When faissR is built with cuVS, the FAISS direct distance call requests FAISS/cuVS dispatch. Two- and three-dimensional Euclidean inputs use the native direct-difference CUDA kernel instead. This avoids float32 cancellation in the algebraically equivalent ||x||^2 + ||q||^2 - 2 x^T q formulation for nearly coincident vectors. Cosine and correlation also use the native CUDA GPU-resident exact route: they are prepared in C++ by row normalization or row-centering plus normalization and then stored as squared L2 divided by two (1 - similarity). Zero-normalized cosine rows and constant correlation rows stop clearly for this GPU-resident path because repairing them on CPU would violate the no-copy contract.

nn_gpu() records the exact-family execution_tuning selected for the GPU-resident route. When method = "auto" would select an approximate CUDA backend for ordinary nn() output, nn_gpu() also records auto_preferred_tuning so downstream packages can inspect the shape/k/metric row, including validation-pending raw inner-product CAGRA/IVF/graph settings, while still receiving device-resident exact-family buffers.

faissR installs <faissR_api.h> and registers the zero-argument faissR_c_api_version callable, which returns ABI version 1. Downstream packages should list faissR in LinkingTo, include this header, verify the ABI, and use its typed getters rather than duplicating callable signatures.

faissR also registers the C-callable faissR_nn_cuda_tuned_gpu_call. Downstream packages can retrieve it with R_GetCCallable("faissR", "faissR_nn_cuda_tuned_gpu_call"). Its current ABI is self-KNN oriented and accepts (x, k, method, metric, include_self, target_recall). The current implementation keeps output on the GPU only for exact CUDA method labels ("auto", "exact", "flat", and "bruteforce"), using the same dimension-aware exact-provider rule as nn_gpu(). The target_recall value is recorded for API symmetry with the tuned NN interface, but recall is exact by construction. Provider-specific GPU-resident outputs for FAISS GPU IVF/CAGRA and direct cuVS IVF/CAGRA/NN-descent require separate persistent ownership of those libraries' result buffers and therefore fail clearly instead of silently returning host matrices. The returned handle external pointer owns the CUDA allocations; its lifetime must cover every downstream access through indices_ptr and distances_ptr.

CPU IVFPQ FastScan is exposed to the R layer only through the float32 adapter: FAISS always receives a float* matrix. For float::fl()/float32 inputs this avoids an R double expansion; ordinary R double matrices are adapted once to row-major float32 for FAISS. The C++ result formatter removes self-neighbours and writes the final column-major R result directly, so the R wrapper does not sort or repair FastScan rows after the search.

KNN results also expose stable list fields for downstream packages: index_base, metric, backend_used, and, on the float32 route, input_layout/input_owns_data. The float package is in Suggests; faissR does not require it unless a user supplies a float32 object or requests float32 distance output.

Raw nn() also keeps a bounded session-local fitted-index cache for CPU FAISS Flat, HNSW, IVF-Flat, IVFPQ, and IVFPQ FastScan routes. The cache key includes the reference matrix fingerprint, dimensions, method, metric, CPU thread count, and fitted-index parameters. Repeated compatible calls reuse the FAISS external pointer and search it directly, so HNSW graph construction, IVF centroid training, inverted-list construction, and IVFPQ/FastScan codebook/PQ-code training are not repeated. nn(..., exclude_self = TRUE) removes self-neighbours inside the C++ fitted-index search path. Metadata reports persistent_index_cache, index_cache_hit, and the usual FAISS reuse fields. Users can disable the cache with options(faissR.cache_fitted_nn_indexes = FALSE) or bound it with options(faissR.cache_fitted_nn_indexes_max_entries = 2L).

faissR also registers a C-callable entry point named faissR_nn_float32_call during package initialization with R_RegisterCCallable(). Downstream packages can retrieve it with R_GetCCallable("faissR", "faissR_nn_float32_call") and call the CPU FAISS Flat float32 route without going through the R wrapper layer. The callable accepts backend = "auto" or CPU/FAISS Flat aliases, including "cpu_faiss_flat", "faiss_flat", and "faiss_flat_l2"; "auto" currently resolves to the CPU FAISS Flat float32 route. It accepts either a float::fl()/float32 object or an ordinary R double matrix; both are adapted once into the row-major float* buffers consumed by FAISS.

For callers that need float32 distances, faissR also registers faissR_nn_float32_call_output. It has the same first six arguments plus a seventh string argument, distances, with values "double" or "float". The original six-argument callable is kept as the stable double-output ABI. Both callables return a stable KNN list with indices, distances, index_base = 1L, distance_type, metric, and backend_used. When distances = "float", the optional float package is required at runtime and the returned distances component is a float::fl()/float32 matrix.

For graph/index implementations that expose L2 but not raw maximum inner product, faissR uses an exact augmented-space reduction. The C++ adapter reads an ordinary R matrix once into float32 or consumes the payload of a float::fl() matrix directly, appends sqrt(max_i ||x_i||^2 - ||x||^2) to reference rows, and appends zero to query rows. L2 ranking in that augmented space is therefore identical to descending raw inner product. The transformed buffers remain row-major float32 and are passed to FAISS/cuVS as float*; no intermediate R double matrix is created. This path is used by inner-product CUDA graph/index routes whose underlying library API is L2-only.

Large Data

Large feature tables can be limited by host memory before the nearest-neighbour algorithm itself becomes the bottleneck. For example, a 1.28M x 1,024 double-precision feature table occupies about 10 GB before R creates the contiguous matrix and backend-side float/index buffers required by FAISS or cuVS. On memory-limited hosts, full-reference searches can be killed by the operating system because of this representation overhead.

For large-scale runs, prefer a matrix, float::fl() object, or on-disk float32 representation so faissR can avoid one large double-to-float expansion. Benchmark reports should separate representation failures from algorithmic failures by recording input type, backend memory, and whether a float32 adapter was used.

Licensing And Acknowledgement

faissR is released under the MIT license. The implementation is inspired by and links against external work including FAISS, FAISS GPU/cuVS integration, RAPIDS cuVS, HNSW, NN-Descent, IVF, product quantization, k-means, NSG, DiskANN/Vamana, and related ANN designs such as GGNN, SONG, BANG, and PilotANN [1-8,13-16,20-29]. See References for papers, software projects, and acknowledgements. External libraries remain separate system dependencies with their own licenses.