Skip to main content

nndex

PyPI | Crates.io

A high-performance Rust library with Python bindings for nearest-neighbor vector search with zero configuration necessary that's so fast, it's faster than numpy! This crate leverages the computational trick where if the source vectors and query vectors are all unit-normalized, performing a dot-product — an operation faster than vector distance calculations — returns the cosine similarity.

Features:

  • CPU backend using rayon parallelism + SIMD (via simsimd), along with highly bespoke compute profiles for maximum CPU performance
  • GPU backend using wgpu compute shaders, supporting Vulkan, Metal, D3D12, and OpenGL graphics APIs
  • Approximate nearest-neighbor (ANN) mode with exact reranking by building an IVF index for even faster lookups
  • Batch search for multiple queries at once
  • Python bindings via PyO3 with numpy, pandas, and polars support
  • Load embeddings directly from .npy, .npz, and .parquet files
  • Internal query-result caching for repeated searches

Disclosure: This library was mostly coded with the assistance of Claude Opus 4.6 and GPT-5.3-Codex as research into the discovery that those models can now successfuly hyperoptimize Rust code. However, I personally have reviewed all code to ensure it is accurate, have added numerous tests and benchmarks to ensure it works as both intended and advertised, and have edited documentation and comments to provide greater signal as to how the package operates. I have given this project the same care and attention as I would give a project I have written from scratch.

Installation

Python

pip install nndex
uv pip install nndex

Rust

Add to your Cargo.toml:

[dependencies]
nndex = "0.2.1"

Python Usage

See also the demo notebooks for more interactive examples.

Building an Index

import numpy as np
from nndex import NNdex

rng = np.random.default_rng(42)
matrix = rng.normal(size=(50_000, 128)).astype(np.float32)

# Build an index (auto-selects GPU if available, falls back to CPU)
index = NNdex(matrix)
print(index.backend)  # "gpu" or "cpu"
print(index.rows, index.dims)  # 50000 128

Single Query

search() returns a tuple of (indices, similarities) as numpy arrays, ordered from most similar to least similar.

query = rng.normal(size=(dims,)).astype(np.float32)

indices, scores = index.search(query, k=5)
print(indices)  # [43576 14100 15993 35409 38916]
print(scores)   # [0.360 0.353 0.335 0.332 0.323]

Batch Query

Pass a 2D array to search multiple queries at once more efficienctly than querying one at a time. Returns 2D numpy arrays.

queries = rng.normal(size=(4, dims)).astype(np.float32)

batch_indices, batch_scores = index.search(queries, k=5)
print(batch_indices.shape)  # (4, 5)
print(batch_scores.shape)   # (4, 5)

Approximate Nearest Neighbors

Enable approx=True for sub-millisecond queries on large matrices: Uses a dimensionality-reduced prefilter followed by exact reranking.

index_ann = NNdex(matrix, approx=True)

indices, scores = index_ann.search(query, k=5)

Backend Selection

# CPU (default)
cpu_index = NNdex(matrix, backend="cpu")

# Force GPU
gpu_index = NNdex(matrix, backend="gpu")

Pre-Normalized Data

If your embeddings are already unit-normalized, set normalized=True to skip the internal normalization step for more computational speed:

normalized_matrix = matrix / np.linalg.norm(matrix, axis=1, keepdims=True)
index = NNdex(normalized_matrix, normalized=True)

DataFrame Output

A conveience function which allows you to pass a pandas or polars DataFrame to dataframe= to get results as DataFrames with a similarity column appended. The DataFrame must have the same number of rows as the index.

import pandas as pd

df = pd.DataFrame(matrix, columns=[f"d{i}" for i in range(dims)])
index = NNdex(matrix, backend="cpu")

# Single query: returns a DataFrame
result = index.search(query, k=3, dataframe=df)

# Batch query: returns a list of DataFrames
results = index.search(queries, k=3, dataframe=df)

This also works with polars DataFrames:

import polars as pl

pldf = pl.DataFrame({f"d{i}": matrix[:, i] for i in range(dims)})
result = index.search(query, k=3, dataframe=pldf)

Loading from Disk

NNdex.from_file() loads embeddings directly from .npy, .npz, or .parquet files in Rust, avoiding Python-side deserialization overhead. .npz and .parquet require a key argument.

# .npy (single 2D array)
index = NNdex.from_file("embeddings.npy")

# .npz (keyed archive)
index = NNdex.from_file("embeddings.npz", key="matrix_store")

# .parquet (list/fixed-size-list column of f32)
index = NNdex.from_file("embeddings.parquet", key="embedding")

Rust Usage

use nndex::{NNdex, IndexOptions, BackendPreference, Neighbor};

fn main() -> Result<(), nndex::NNdexError> {
    let matrix = vec![
        1.0_f32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
        1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
    ];

    let index = NNdex::new(&matrix, 3, 8, IndexOptions {
        normalized: false,
        approx: false,
        backend: BackendPreference::Cpu,
        ..IndexOptions::default()
    })?;

    let query = vec![0.8_f32, 0.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
    let neighbors = index.search(&query, 2)?;

    for neighbor in &neighbors {
        println!("index: {}, similarity: {:.4}", neighbor.index, neighbor.similarity);
    }

    Ok(())
}

Benchmarks

[BENCHMARK IMAGES TO BE ADDED]

Notes

  • nndex is NOT a vector store/database which implies that the vectors can be created/updated/deleted from the matrix, and it is not intending to be. It's intended to be used with a fixed matrix of data, although this crate is so fast that you could reinitialize the NNdex without much overhead if needed.
  • For Apple Silicon in particular, the use of the GPU backend (Metal) is not recommended below 100k rows due to the dispatch overhead of wgpu being greater than the inference speed. This is not the case with discrete GPUs.
  • BLAS is only supported for macOS because the underlying BLAS library (accelerate) is included by default. There are tradeoffs for Linux/Windows and I am still determining what to do there.

API Reference

Python

NNdex(data, ...)

Parameter Type Default Description
data array-like required 2D numpy array, list, or pandas/polars DataFrame
normalized bool False Skip internal normalization if data is already unit-norm
approx bool False Enable ANN prefiltering with exact reranking
backend str "cpu" "cpu" or "gpu"
enable_cache bool True Cache repeated query results

NNdex.from_file(path, ...)

Parameter Type Default Description
path str required Path to .npy, .npz, or .parquet file
key str/None None Array key (.npz) or column name (.parquet)
normalized bool False Skip internal normalization if data is already unit-norm
approx bool False Enable ANN prefiltering with exact reranking
backend str "cpu" "cpu" or "gpu"
enable_cache bool True Cache repeated query results

index.search(query, k=10, dataframe=None)

Parameter Type Default Description
query array-like required 1D vector or 2D matrix of queries
k int 10 Number of neighbors to return per query
dataframe DataFrame/None None Source DataFrame for output; must match index row count

Returns: Without dataframe: tuple of (indices, similarities) as numpy arrays (1D for single query, 2D for batch). With dataframe: a DataFrame (single query) or list of DataFrames (batch) with a similarity column.

Properties

Property Type Description
backend str Active backend ("cpu"/"gpu")
rows int Number of indexed rows
dims int Number of dimensions per row

Rust

NNdex::new(matrix, rows, dims, options)

Constructs an index from a flattened row-major &[f32] matrix.

index.search(query, k) -> Result<Vec<Neighbor>>

Returns top-k neighbors sorted by descending cosine similarity.

index.search_batch(queries, query_rows, k) -> Result<Vec<Vec<Neighbor>>>

Batch search over multiple query vectors.

IndexOptions

Field Type Default Description
normalized bool false Skip normalization for pre-normalized data
approx bool false Enable ANN prefiltering
backend BackendPreference Cpu Cpu, or Gpu
enable_cache bool true Cache constructor and query results

Maintainer/Creator

Max Woolf (@minimaxir)

Max's open-source projects are supported by his Patreon and GitHub Sponsors. If you found this project helpful, any monetary contributions to the Patreon are appreciated and will be put to good creative use.

License

MIT

Metadata

Release files for nndex 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nndex 0.2.1
File Size Uploaded
nndex-0.2.1.tar.gz 100.1 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for nndex 0.2.1
File
nndex-0.2.1-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
nndex-0.2.1-cp310-abi3-manylinux_2_35_x86_64.whl CPython 3.10 abi3 Linux glibc 2.35+ x86-64 Details
nndex-0.2.1-cp310-abi3-manylinux_2_35_aarch64.whl CPython 3.10 abi3 Linux glibc 2.35+ ARM64 Details
nndex-0.2.1-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
nndex-0.2.1-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 23.6 MB

Release files / nndex-0.2.1.tar.gz

Download URL nndex-0.2.1.tar.gz
Size 100.1 kB
Tags Source
SHA-256 checksum
How to use checksums
9bf8cd99d86fb364d88f51572eb3d5a024dd9a38e8e8ea2575eb3273cf68e111
BLAKE2b-256 checksum
How to use checksums
5be74e6ea90cfabbfb550ea56c3c1be7612302a0626bd1a64c950a0b4287a069
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / nndex-0.2.1-cp310-abi3-win_amd64.whl

Download URL nndex-0.2.1-cp310-abi3-win_amd64.whl
Size 4.9 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
ec6f666c81b0106bed3fee0e7e0e3e5d709caf7f4a0168da8c1ac8207cc78b1d
BLAKE2b-256 checksum
How to use checksums
11c4cca8f11434f1222ccf677a4c6fde865a72d5576fce6e385a48efc3f52772
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / nndex-0.2.1-cp310-abi3-manylinux_2_35_x86_64.whl

Download URL nndex-0.2.1-cp310-abi3-manylinux_2_35_x86_64.whl
Size 5.2 MB
Tags CPython 3.10 Linux glibc 2.35+ x86-64 abi3
SHA-256 checksum
How to use checksums
0ce7137fdcf6c31b574f1b379ac1716d384b477e20dd0e478009e7a26e13ade0
BLAKE2b-256 checksum
How to use checksums
d8fc89b32f2ef52615eeed9fb93c64d999ee8b5f1fea6ffa7714ace95290882f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / nndex-0.2.1-cp310-abi3-manylinux_2_35_aarch64.whl

Download URL nndex-0.2.1-cp310-abi3-manylinux_2_35_aarch64.whl
Size 4.9 MB
Tags CPython 3.10 Linux glibc 2.35+ ARM64 abi3
SHA-256 checksum
How to use checksums
948fe2713d34caf7c9e1b1f9366d49932857244ec2636001ae6a2dbbc07cf603
BLAKE2b-256 checksum
How to use checksums
749309c5872e3284ab37c7eb231c1458847a74578b2580dc92bc20ff22b1c257
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / nndex-0.2.1-cp310-abi3-macosx_11_0_arm64.whl

Download URL nndex-0.2.1-cp310-abi3-macosx_11_0_arm64.whl
Size 4.0 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
6bf0054921e3add0165f541e61c93d2ebc263d6abd2484c79d3b6e5b431a4295
BLAKE2b-256 checksum
How to use checksums
bc37abac4a1c1a416ba3e558679643598ee33b1f50c1e2234142f83fc5d078a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / nndex-0.2.1-cp310-abi3-macosx_10_12_x86_64.whl

Download URL nndex-0.2.1-cp310-abi3-macosx_10_12_x86_64.whl
Size 4.5 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
8c627d3b90806a46d5aa29d1b1e0d71e4216afe9f40de520286cb50a49202e4b
BLAKE2b-256 checksum
How to use checksums
2e4071f65438257fa64a1a94c4bffc8fe42c985ed0015057c16b4bbdea76651b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

0.2.1 This release

6 release files

0.2.0

6 release files

0.1.0

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page