elips/docs
Reference · Python

GPU API

ELIPS can offload distance and top-k computation to the GPU. The current acceleration surface is deliberately small: discover devices with elips.accelerators(), pick a backend when you open a client with device=, and run exact GPU brute-force search with elips.bf_knn(...). See the C++ GPU Engine internals and the Python SDK Base API for more context.

Backends & Maturity

ELIPS ships distance and top-k kernels for the backends below. There are no other GPU backends.

  • Metal — production. Default ON for Apple Silicon; provides the distance and top-k kernels used by GPU search.
  • CUDA — experimental. Requires the NVIDIA toolkit.
  • HIP / ROCm — experimental. Requires ROCm.
  • CPU — always available fallback.

GPU kernels cover distance computation and top-k selection only. The rest of the index pipeline runs on the CPU.

Installation & Builds

  • Metal: Apple Silicon only. Gated by -DELIPS_GPU_METAL=ON (defaults to ON for Apple platforms). Do not force this on Linux.
  • CUDA: requires the NVIDIA toolkit.
  • HIP: requires ROCm.

Detecting GPU Support

elips.has_gpu is a boolean that is True when the current build was compiled with at least one GPU backend and a device is present.

python
import elips

if elips.has_gpu:
    print("GPU acceleration available")
else:
    print("CPU-only build or no device")

Device Discovery

  • elips.accelerators() -> list[AcceleratorSpec] — every GPU detected across the compiled backends.
  • elips.accelerator() -> AcceleratorSpec | None — the device ELIPS would pick by default, or None on a CPU-only machine.
python
import elips

for spec in elips.accelerators():
    print(spec.backend, spec.index, spec.name, spec.memory_bytes)

best = elips.accelerator()
if best is None:
    print("no GPU available")
else:
    print(f"default device: {best.name} ({best.backend})")

AcceleratorSpec

A discovery record describing one GPU. Fields:

  • name (str) — human-readable device name.
  • backend (str) — one of "metal", "cuda", "hip".
  • index (int) — the device ordinal within its backend (the N in "cuda:N").
  • memory_bytes (int) — total device memory.
  • free_memory_bytes (int) — currently free device memory.
  • unified_memory (bool) — whether the device shares memory with the host (true on Apple Silicon).
  • supports_fp16 (bool) — half-precision support.
  • raw — the backend-specific raw descriptor.

Selecting a Device

Choose a backend when you open a client with the device= keyword. Valid values are "cpu", "auto", "metal", "cuda:N", and "hip:N". There is no gpu=True keyword.

python
import elips

# Let ELIPS choose the best available device
client = elips.Client(dimension=128, device="auto")

# Pin a specific backend/device
client = elips.Client(dimension=128, device="metal")
client = elips.Client(dimension=128, device="cuda:0")

Requesting a named backend that is absent raises BackendUnavailableError — unless you pass fallback=True, in which case ELIPS silently falls back to the CPU.

python
try:
    client = elips.Client(dimension=128, device="cuda:0")
except elips.BackendUnavailableError:
    client = elips.Client(dimension=128, device="cpu")

# Or ask for fallback up front
client = elips.Client(dimension=128, device="cuda:0", fallback=True)

Advanced device policy can also be supplied as a gpu=GpuConfig(...) object, but the device= string is the recommended selector for almost all use cases.

Native Device Info

The high-level client does not expose gpu_info() directly. Reach the detailed device report through the native handle:

python
client = elips.Client(dimension=128, device="auto")
info = client.native.gpu_info()
print(info)

GPU Brute-Force kNN

elips.bf_knn(xq, xb, k, metric="l2", device="auto") runs an exact brute-force k-nearest-neighbour search with no index build. It returns a tuple (D, I) of float32 distances and int64 labels. device="metal" is the Apple-GPU path; metric strings are FAISS-style ("l2" for euclidean). This is available in GPU-enabled builds only.

python
import numpy as np, elips

xb = np.random.rand(100000, 128).astype("float32")  # corpus
xq = np.random.rand(1000, 128).astype("float32")    # queries

# Exact GPU brute-force, no index build
D, I = elips.bf_knn(xq, xb, 10, metric="l2", device="metal")
print(D.shape, I.shape)  # (1000, 10) (1000, 10)

On an Apple M4 (dim=128, 100k corpus, 1k queries, k=10, L2), batched bf_knn on Metal reaches recall 1.000 at roughly 914 QPS; single-query latency is far lower (~24 QPS), so batch your queries to amortize kernel launch and transfer overhead.

C++ GPU Types

If you drop into the C++ layer, the GPU abstractions were renamed: GpuBackend (was GpuPort), BackendSelector (was GpuSelector), and QueryBatcher (was DynamicBatcher). See the GPU Engine internals.

Common Pitfalls

  • No gpu=True: select hardware with device="metal"|"cuda:N"|"hip:N"|"auto"|"cpu", not a boolean flag.
  • Absent backends raise: a named backend that is not present raises BackendUnavailableError unless fallback=True is set.
  • Batch your queries: single-query GPU calls are dominated by launch and transfer overhead; batch for throughput.
  • Metal is Apple-only: the Metal backend defaults OFF on non-Apple systems — do not force it on Linux builds.
  • Check for None: elips.accelerator() returns None on CPU-only machines.