hnswlib-rs
Library Featured
Rust implementation of the HNSW algorithm (Malkov-Yashunin)
- Downloads
- 226,188
- Dependents
- 86
- Stars
- 257
- Forks
- 48
- Contributors
- 6
- Commits
- 225
- Latest release
- 0.3.5 ·
About
hnsw-rs
This crate provides a Rust implementation of the paper by Yu.A. Malkov and D.A Yashunin:
"Efficient and Robust approximate nearest neighbours using Hierarchical Navigable Small World Graphs" (2016,2018) arxiv
Functionalities
The crate is built on top of the anndists and can use the following distances:
Read the full README
usual distances as L1, L2, Cosine, Jaccard, Hamming for vectors of standard numeric types, Levenshtein distance on u16.
Hellinger distance and Jeffreys divergence between probability distributions (f32 and f64). It must be noted that the Jeffreys divergence (a symetrized Kullback-Leibler divergence) do not satisfy the triangle inequality. (Neither Cosine distance !).
Jensen-Shannon distance between probability distributions (f32 and f64). It is defined as the square root of the Jensen-Shannon divergence and is a bounded metric. See Nielsen F. in Entropy 2019, 21(5), 485.
A Trait to enable the user to implement its own distances. It takes as data slices of types T satisfying T:Serialize+Clone+Send+Sync. It is also possible to use C extern functions or closures.
An interface towards C and more specifically to the Julia language. See the companion Julia package HnswAnn.jl and the building paragraph for some help for Julia users.
The hnsw implementation provides:
Multithreaded insertion and search requests.
Dump and reload functions (See module hnswio) to store the data and the graph once it is built. These facilities rely partly on Serde so T needs to implement Serialize and Deserialized as derived by Serde. It is also possible to reload only the graph and not the data themselves. A specific type (struct NoData, associated to the NoDist distance is dedicated to this functionality.
A flattening conversion of the Hnsw structure to keep only neighborhood relationships between points (without their internal data) internal to the Hnsw structure (see module flatten.rs, FlatPoint and FlatNeighborhood). It is thus possible to keep some topology information with low memory usage.
Filtering: It is possible to add filters so only results which satisfies the filter is in the result set. The filtering is done during the search, so it is not a post filter. There is currently two ways of using the filter, one can add allowed ids in a sorted vector and send as a parameter, or one can define a function which will be called before an id is added to the result set.
Examples on both these strategies are in the examples or tests directory. One can also implement the trait Filterable for new types, if one would like the filter to be kept in a bitvector, for example.Possibilty to use mmap on dumped data (not on graph part) which is useful for large data vectors. This enables coreset and clusters computation in streaming, see coreset and soon on crates.io.
Implementation
The graph construction and searches are multithreaded with the parking_lot crate (See parallel_insert_data and parallel_search_neighbours functions and also examples files). Distances are provided by the crate anndists, see Building.
Building
Simd
Two features activate simd in the crate anndists :
The feature "simdeez_f" provide simd for x86_64 processors. Compile with cargo build --release --features "simdeez_f" or change the default features in Cargo.toml. To compile this crate on a M1 chip just do not activate this feature.
The feature "stdsimd" provides portable simd through std::simd but requires rust nightly.
Setting this feature in features default (or by cargo command) activates the portable_simd feature on rust nightly. Not all couples (Distance, type) are provided yet. (See the crate anndists)
Julia interface
By default the crate is a standalone project and builds a static libray and executable. To be used with the companion Julia package it is necessary to build a dynamic library. This can be done by just uncommenting (i.e get rid of the #) in file Cargo.toml the line:
#crate-type = ["cdylib"]
and rerun the command: cargo build --release.
This will generate a .so file in the target/release directory.
Algorithm and Input Parameters
The algorithm stores points in layers (at most 16), and a graph is constructed to enable a search from less densely populated levels to most densely populated levels by constructing links from less dense layers to the most dense layer (level 0).
Roughly the algorithm goes along runs as follows:
Upon insertion, the level l of a new point is sampled with an exponential law, limiting the number of levels to 16,
so that level 0 is the most densely populated layer, upper layers being exponentially less populated as level increases.
The nearest neighbour of the point is searched in lookup tables from the upper level to the level just above its layer (l), so we should arrive near the new point at its level at a relatively low cost. Then the max_nb_connection nearest neighbours are searched in neighbours of neighbours table (with a reverse updating of tables) recursively from its layer l down to the most populated level 0.
The parameter of the exponential law to sample point levels is set to ln(max_nb_connection)/scale.
By default scale is set to 1. It is possible to reduce the scale parameter and thus reduce the number of levels used (See Hnsw::modify_level_scale) without increasing max_nb_connection.
This often provide better recalls without increasing max_nb_connection and thus spare memory usage. (See examples)
The main parameters occuring in constructing the graph or in searching are:
max_nb_connection (in hnsw initialization) The maximum number of links from one point to others. Values ranging from 16 to 64 are standard initialising values, the higher the more time consuming. Note that layer 0 can store 2 * max_nb_connection neighbours
ef_construction (in hnsw initialization)
This parameter controls the width of the search for neighbours during insertion. Values from 200 to 800 are standard initialising values, the higher the more time consuming. It should be greater than 2 * max_nb_connection neighbours.max_layer (in hnsw initialization)
The maximum number of layers in graph. Must be less or equal than 16.ef_arg (in search methods)
This parameter controls the width of the search in the lowest level, it must be greater than number of neighbours asked but can be less than ef_construction. As a rule of thumb could be between the number of neighbours we will ask for (knbn arg in search method) and max_nb_connection.keep_pruned and extend_candidates.
These parameters are described in the paper by Malkov and Yashunin can be used to modify the search strategy. The interested user should check the paper to see the impact. By default the values are as recommended in the paper.
Benchmarks and Examples (examples)
Some examples are taken from the ann-benchmarks site
and recall rates and request/s are given in comments in the examples files for some input parameters.
The annhdf5 module implements reading the standardized data files
of the ann-benchmarks site,
just download the necessary benchmark data files and modify path in sources accordingly.
Then run: cargo build --release --features="simdeez_f" --examples .
It is possible in these examples to change from parallel searches to serial searches to check for speeds
or modify parameters to see the impact on performance.
With a i9-13900HX 24 cores laptop we get the following results:
- fashion-mnist-784-euclidean : search requests run at 62000 req/s with a recall rate of 0.977
- ann-glove-25-angular : search for the first 100 neighbours run with recall 0.979 at 12000 req/s
- sift1m benchmark: (1 million points in 128 dimension) search requests for the 10 first neighbours runs at 15000 req/s with a recall rate of 0.9907 or at 8300 req/s with a recall rate of 0.9959, depending on the parameters.
With AMD ryzen 9 7950X 16-Core (32 threads) and feature simdeez (3.0) enabled we get:
- sift1m benchmark: with level scale modification factor 0.5 we get a recall@10 of 0.9992 at 11700 req/s. Insertion time is 600s.
Moreover a tiny crate bigann gives results on the first 10 Million points of the BIGANN benchmark. The benchmark is also described at IRISA. This crate can used to play with parameters on this data. Results give a recall between 0.92 and 0.99 depending on number of requests and parameters.
Some lines extracted from this Mnist benchmark show how it works for f32 and L2 norm
// reading data
let anndata = AnnBenchmarkData::new(fname).unwrap();
let nb_elem = anndata.train_data.len();
let max_nb_connection = 24;
let nb_layer = 16.min((nb_elem as f32).ln().trunc() as usize);
let ef_c = 400;
// allocating network
let mut hnsw = Hnsw::<f32, DistL2>::new(max_nb_connection, nb_elem, nb_layer, ef_c, DistL2{});
hnsw.set_extend_candidates(false);
// parallel insertion of train data
let data_for_par_insertion = anndata.train_data.iter().map( |x| (&x.0, x.1)).collect();
hnsw.parallel_insert(&data_for_par_insertion);
//
hnsw.dump_layer_info();
// Now the bench with 10 neighbours
let mut knn_neighbours_for_tests = Vec::<Vec<Neighbour>>::with_capacity(nb_elem);
hnsw.set_searching_mode(true);
let knbn = 10;
let ef_c = max_nb_connection;
// search 10 nearest neighbours for test data
knn_neighbours_for_tests = hnsw.parallel_search(&anndata.test_data, knbn, ef_c);
....
Contributions
Sannsyn contributed to Drop implementation and FilterT trait. Petter Egesund added the DistLevenshtein distance.
Evolutions are described here
License
Licensed under either of
- Apache License, Version 2.0, LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0
- MIT license LICENSE-MIT or http://opensource.org/licenses/MIT
at your option.
Compatibility
- Runs on
- GPU support unknown
What shows it
- no dependency or feature matches a backend row
- Native code
- None found
- No native code found
- Python bindings
- No Python binding crate found
- License
- Apache-2.0 OR MIT
Compare with similar projects
Order: strongest tier first, then most downloads, then name, with unmeasured values last. A library counts downloads over 30 days. An app counts binary downloads per release. An app with none counts its crate downloads over 30 days. ML Rust runs this catalogue, so its closest project always gets a row.
| Project | Downloads | Dependents | Stars | Latest release | Runs on (GPU) | Native code | License |
|---|---|---|---|---|---|---|---|
|
Rust implementation of the HNSW algorithm (Malkov-Yashunin) |
226,188 downloads over 30 days | 86 | 257 | 0.3.5 | GPU support unknown | No native code found | Apache-2.0 OR MIT |
|
kdtree-rs Featured
K-dimensional tree in Rust for fast geospatial indexing and nearest neighbors lookup |
388,481 downloads over 30 days | 29 | 286 | 0.8.1 | GPU support unknown | No native code found | Apache-2.0 OR MIT |
|
surrealdb Featured
A scalable, distributed, collaborative, document-graph database, for the realtime web |
197,581 downloads over 30 days | 162 | 33,296 | 4.0.0 | GPU support unknown | Binds native code: ring, openssl-sys (feature native-tls) | Not measured |
|
ruvector Listed
High-performance Rust vector database core with HNSW indexing |
39,411 downloads over 30 days | 51 | 4,675 | 2.5.0 | CUDA, WebGPU, Metal | Binds native code: libsqlite3-sys, napi-sys, ort-sys, pgrx-pg-sys, pqcrypto-internals and 5 more | MIT |
|
splintr Listed
Tokenizer for Rust and Python: pure Rust by default, with 18 bundled vocabularies and four backends covering BPE, SentencePiece and WordPiece. Its output matches Hugging Face tokenizers id for id on 14 model families, including GPT-2, Qwen, Whisper, T5 and BERT. Python bindings |
8,204 downloads over 30 days | 7 | 63 | 0.22.2 | GPU support unknown | Binds native code: pcre2-sys (feature pcre2) | MIT |
|
instant-distance Archived
Fast minimal implementation of HNSW maps for approximate nearest neighbors searches |
96,520 downloads over 30 days | 36 | 343 | 0.6.1 | GPU support unknown | No native code found | Apache-2.0 OR MIT |
|
chromadb-rs Archived
A Rust client library for the ChromaDB vector database. |
3,548 downloads over 30 days | 7 | 46 | 2.3.0 | GPU support unknown | No native code found | MIT |
Why FeaturedIt passes every Featured gate.
What it meets
- It has 59 active dependents and 53 new dependents in 365 days. Featured needs 11 active dependents or 9 new dependents in 365 days.
- Active dependents are at 99% of their peak. Featured needs at least 50%.
- Docs are present. The release commit or a manifest ties the crate to the repository.
- No vulnerability advisory stays unpatched past the grace window. At least one version is neither a prerelease nor 0.0.x.
- The last activity is on 6 Nov 2026, 3 days ago. The rules expect an update within 1800 days. Dormancy rule
Exact rules: Featured library. The check on applied thresholds version 9.
Every rule outcome, as the check records it
expected-interval: version 0.3.5 base 450, doubled for 226188 downloads per month, doubled for 59 active users: E = 1800 daysdormancy: last activity 2026-11-06, 3 days ago, E = 1800: activefeatured: featured: active_users 59, new_adopters 53, former_glory 0.99
Measurements and sources
Each signal row shows the raw value, its source and the fetch date. The Sources list gathers every distinct source on this page. Movement needs more history.
Sources6 sources
- Description: the GitHub repository description
- crates.io database dump, fetched
- RustSec advisory database, fetched
- GitHub repository, fetched
- crates.io package, fetched
- GitHub contributors, fetched
Adoption59 active dependents, 53 new dependents in 365 days1 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Active dependents | 59 | crates.io database dump | Yes | |
| New dependents (365 days) | 53 | crates.io database dump | Yes | |
| Peak active dependents | 61 | crates.io database dump | Yes | |
| Active dependents as a share of the peak | 0.9859 | crates.io database dump | Yes | |
| Binary downloads per release | Not measured | Yes |
MaintenanceLatest human commit: 3 days ago3 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Latest human commit | 2026-11-06 | GitHub repository | Yes | |
| Latest release | 2026-10-09 | crates.io database dump | Yes | |
| First release | 2020-01-13 | crates.io database dump | Yes | |
| Latest version | 0.3.5 | crates.io database dump | Yes | |
| Days the oldest outside issue waits for a reply | 2346 | GitHub repository | Yes | |
| Maintainer replies over 365 days | 20 | GitHub repository | Yes | |
| Repository archived | false | GitHub repository | Yes | |
| Repository gone | false | GitHub repository | Yes | |
| Release commit or manifest in the repo | true | crates.io package | Yes | |
| Published docs | true | crates.io database dump | Yes | |
| Latest release fails cargo check | Not measured | Yes | ||
| Build check date | Not measured | Yes | ||
| Build check toolchain | Not measured | Yes |
SecurityUnpatched vulnerabilities: none
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Unpatched vulnerabilities past the grace window | none | RustSec advisory database | Yes | |
| RustSec unmaintained advisory | none | RustSec advisory database | Yes | |
| Deprecation notice | none | crates.io database dump | Yes | |
| Deprecated requirement | none | crates.io database dump | Yes |
Distribution28 releases, 226,188 downloads over 30 days5 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Releases | 28 | crates.io database dump | Yes | |
| Downloads over 30 days | 226188 | crates.io database dump | Yes | |
| Binary downloads over the last 3 releases | Not measured | Yes | ||
| Homebrew installs on request over 365 days | Not measured | Yes | ||
| Repology repository families | Not measured | Yes | ||
| Docker pull growth over 90 days | Not measured | Yes | ||
| Docker pulls at the growth baseline | Not measured | Yes | ||
| Every version a prerelease | false | crates.io database dump | Yes | |
| Every version 0.0.x | false | crates.io database dump | Yes | |
| Every version yanked | false | crates.io database dump | Yes | |
| Crate deleted from crates.io | false | crates.io database dump | Yes |
Repository257 stars, 48 forks, 6 contributors, 225 commits. Stars alone never qualify a project.
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| GitHub stars | 257 | GitHub repository | Yes — one part of the repository route | |
| GitHub forks | 48 | GitHub repository | Yes — one part of the repository route | |
| Contributors with a GitHub account | 6 | GitHub contributors | Yes — one part of the repository route | |
| Commits on the default branch | 225 | GitHub repository | No | |
| Repository's main crate left out of the entry | none | crates.io database dump | Yes |
Facts that never count
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| License | Apache-2.0 OR MIT | crates.io database dump | No | |
| Runs on, from listed crates and their direct dependencies | GPU support unknown | crates.io database dump | No | |
| What shows each backend | no dependency or feature matches a backend row | crates.io database dump | No | |
| Native code, from listed crates and their dependencies | No native code found | crates.io database dump | No | |
| Python bindings, from direct dependencies of listed crates | No Python binding crate found | crates.io database dump | No | |
| Crates that depend on a listed crate directly | 86 | crates.io database dump | No |
Corrections and review history
No correction applies to this entry.
Judgments
A judgment enters only through an approved issue and a merged pull request.
No judgment applies to this entry.