init_index 需预先给定 max_elements,超出容量会抛异常;set_ef 设置的 ef 不随索引保存,加载后需手动重设;Python 多线程下带 filter 的搜索较慢,建议设 num_threads=1;当前版本为 0.10.0rc2 预发布,勿当作稳定版 0.10.0 使用
pip install hnswlibREADME 未说明显存或 GPU 要求,仅提到用 num_threads 控制 CPU 线程数
轻量级 header-only 的 HNSW 近似最近邻库,C++ 与 Python 都能直接用
纯头文件的 C++/Python 近似最近邻检索库,实现 HNSW 算法,无需额外依赖即可嵌入项目,用于向量相似度搜索。
核心能力
- 纯头文件实现,除 C++11 外无其他依赖
- 支持增量插入、更新与标记删除,Python 索引可 pickle
- 提供 l2、内积、cosine 三种距离,C++ 可自定义距离
- 支持按标签过滤搜索与多线程插入查询
- 相比 nmslib 实现内存占用更小、构建更快
容易踩的坑
- v0.10.0-rc.2pre-release2026-09-14 · 2026-09-14 11:50 UTC
查看更新说明
What's Changed
- Prepare v0.10.0-rc.2: fix rc.1 by @ilyajob05 in https://github.com/nmslib/hnswlib/pull/682
Full Changelog: https://github.com/nmslib/hnswlib/compare/v0.10.0-rc.1...v0.10.0-rc.2
- v0.10.0-rc.1pre-release2026-09-13 · 2026-09-13 11:01 UTC
查看更新说明
What's Changed
- Optional no-exceptions C++ API: *NoExceptions methods return Status / StatusOr so the headers can be compiled with -fno-exceptions (-DHNSWLIB_ENABLE_EXCEPTIONS=OFF). Throwing methods remain the default. (#619, #678) by @michaelbautin
- Stream saveIndex / loadIndex over std::ostream / std::istream, plus getInternalIdByLabel
- CI covers exceptions on/off, Clang / GCC / MSVC, and ASAN / UBSAN
- Fixed Clang UBSan misaligned label store in addPoint
- Report addPoint capacity and stream write errors via Status
Full Changelog: https://github.com/nmslib/hnswlib/compare/v0.9.0...v0.10.0-rc.1
- v0.9.0最新正式版2026-03-28 · 2026-03-28 22:48 UTC
Fixed incorrect results in bruteforce search with filter (#514) by @lukaszsmolinski Fixed missing normalization check in BFIndex (#514) by @lukaszsmolinski Throw an exception when fewer than k elements are available (#514) by @lukaszsmolinski Remove unused variable (#531) by @lulyon Change cosine similarity to distance in README by @yurymalkov
What's Changed
- Fix bruteforce results, update CI, and version bump to 0.9.0 by @ilyajob05 in https://github.com/nmslib/hnswlib/pull/658
Full Changelog: https://github.com/nmslib/hnswlib/compare/v0.8.0...v0.9.0
- v0.8.02023-12-03 · 2023-12-03 03:49 UTC
查看更新说明
Highlights:
- Multi-vector document search and epsilon search (for now, only in C++)
- By default, there is no statistic aggregation, which speeds up the multi-threaded search (it does not seem like people are using it anyway: Issue #495).
- Various bugfixes and improvements
get_itemsnow havereturn_typeparameter, which can be either 'numpy' or 'list'
Huge thanks to the contributors:
- Dmitry Yashunin (dyashuni) - Led the addition of a custom stop condition, multivector and epsilon search functionalities, and various code refinements and bug fixes.
- Alexander Vieth (alxvth) - Contributed to resolving global linkage issues and improving Mac setup in the CI environment.
- Taras Tsugrii (ttsugriy) - Focused on fixing build warnings, enhancing bruteforce removePoint functionality, and improving memory management.
- aurora327 - Implemented more efficient AVX512 instruction sets in functions.
- Étienne Mollier (emollier) - added a cap on the 'M' parameter and a typo.
- Johan Rade (jrade) - Addressed a linking error when compiling with Visual Studio.
- James Melville (jlmelville) - Addressed reordering warning, sign mismatch in loops, and standardized error handling using macros (#508 has the details)
- stephematician - Resolved initialization order warning in GNU compilers.
- moritz-h - Added CMake install targets and set CMake version range.
- drons - Implemented functions for precise memory footprint control and fixed memory leak issues.
- Atsushi Tatsuma (yoshoku) - Added a missing virtual destructor to BaseFilterFunctor.
- v0.7.02023-02-07 · 2023-02-07 20:10 UTC
查看更新说明
version 0.7.0
- Added support to filtering (#402, #430) by @kishorenc
- Added python interface for filtering (though note its performance is limited by GIL) (#417) by @gtsoukas
- Added support for replacing the elements that were marked as delete with newly inserted elements (to control the size of the index, #418) by @dyashuni
- Fixed data races/deadlocks in updates/insertion, added stress test for multithreaded operation (#418) by @dyashuni
- Documentation, tests, exception handling, refactoring (#375, #379, #380, #395, #396, #401, #406, #404, #409, #410, #416, #415, #431, #432, #433) by @jlmelville, @dyashuni, @kishorenc, @korzhenevski, @yoshoku, @jianshu93, @PLNech
- global linkages (#383) by @MasterAler, USE_SSE usage in MSVC (#408) by @alxvth
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Hnswlib - fast approximate nearest neighbor search
Header-only C++ HNSW implementation with python bindings, insertions and updates.
NEWS:
version 0.10.0rc2 (release candidate — not a stable PyPI upload)
- Package version is
0.10.0rc2(PEP 440); do not upload as final0.10.0yet. Supersedes v0.10.0-rc.1. - Stream
loadIndexNoExceptionsfails closed on an unopened/failed input without clearing a live index searchKnnCloserFirstisconstagain;addPoint(..., int level)is restored so integer levels are not treated asreplace_deleted- CMake no longer wipes caller
CMAKE_CXX_FLAGS(includingadd_subdirectory/ examples-off) StatusOrstores T inline and moves the result;*NoExceptionsstream writes returnStatusinstead of throwing on write failure- Optional no-exceptions C++ API:
*NoExceptionsmethods returnStatus/StatusOrso the headers can be compiled with-fno-exceptions(-DHNSWLIB_ENABLE_EXCEPTIONS=OFF). Throwing methods remain the default. (#619, #678) by @michaelbautin - Stream
saveIndex/loadIndexoverstd::ostream/std::istream, plusgetInternalIdByLabel - CI covers exceptions on/off, Clang / GCC / MSVC, and ASAN / UBSAN
- Fixed Clang UBSan misaligned label store in addPoint
- Report addPoint capacity and stream write errors via Status
version 0.9.0
- Fixed incorrect results in bruteforce search with filter (#514) by @lukaszsmolinski
- Fixed missing normalization check in BFIndex (#514) by @lukaszsmolinski
- Throw an exception when fewer than k elements are available (#514) by @lukaszsmolinski
- Remove unused variable (#531) by @lulyon
- Change cosine similarity to distance in README by @yurymalkov
version 0.8.0
- Multi-vector document search and epsilon search (for now, only in C++)
- By default, there is no statistic aggregation, which speeds up the multi-threaded search (it does not seem like people are using it anyway: Issue #495).
- Various bugfixes and improvements
get_itemsnow havereturn_typeparameter, which can be either 'numpy' or 'list'
Full list of changes: https://github.com/nmslib/hnswlib/pull/523
version 0.7.0
- Added support to filtering (#402, #430) by @kishorenc
- Added python interface for filtering (though note its performance is limited by GIL) (#417) by @gtsoukas
- Added support for replacing the elements that were marked as delete with newly inserted elements (to control the size of the index, #418) by @dyashuni
- Fixed data races/deadlocks in updates/insertion, added stress test for multithreaded operation (#418) by @dyashuni
- Documentation, tests, exception handling, refactoring (#375, #379, #380, #395, #396, #401, #406, #404, #409, #410, #416, #415, #431, #432, #433) by @jlmelville, @dyashuni, @kishorenc, @korzhenevski, @yoshoku, @jianshu93, @PLNech
- global linkages (#383) by @MasterAler, USE_SSE usage in MSVC (#408) by @alxvth
Highlights:
- Lightweight, header-only, no dependencies other than C++ 11
- Interfaces for C++, Python, external support for Java and R (https://github.com/jlmelville/rcpphnsw).
- Has full support for incremental index construction and updating the elements (thanks to the contribution by Apoorv Sharma). Has support for element deletions (by marking them in index, later can be replaced with other elements). Python index is picklable.
- Can work with custom user defined distances (C++).
- Significantly less memory footprint and faster build time compared to current nmslib's implementation.
Description of the algorithm parameters can be found in ALGO_PARAMS.md.
Python bindings
Supported distances:
| Distance | parameter | Equation |
|---|---|---|
| Squared L2 | 'l2' | d = sum((Ai-Bi)^2) |
| Inner product | 'ip' | d = 1.0 - sum(Ai*Bi) |
| Cosine distance | 'cosine' | d = 1.0 - sum(Ai*Bi) / sqrt(sum(Ai*Ai) * sum(Bi*Bi)) |
Note that inner product is not an actual metric. An element can be closer to some other element than to itself. That allows some speedup if you remove all elements that are not the closest to themselves from the index.
For other spaces use the nmslib library https://github.com/nmslib/nmslib.
API description
hnswlib.Index(space, dim)creates a non-initialized index an HNSW in spacespacewith integer dimensiondim.
hnswlib.Index methods:
-
init_index(max_elements, M = 16, ef_construction = 200, random_seed = 100, allow_replace_deleted = False)initializes the index from with no elements.max_elementsdefines the maximum number of elements that can be stored in the structure(can be increased/shrunk).ef_constructiondefines a construction time/accuracy trade-off (see ALGO_PARAMS.md).Mdefines tha maximum number of outgoing connections in the graph (ALGO_PARAMS.md).allow_replace_deletedenables replacing of deleted elements with new added ones.
-
add_items(data, ids, num_threads = -1, replace_deleted = False)- inserts thedata(numpy array of vectors, shape:N*dim) into the structure.num_threadssets the number of cpu threads to use (-1 means use default).idsare optional N-size numpy array of integer labels for all elements indata.- If index already has the elements with the same labels, their features will be updated. Note that update procedure is slower than insertion of a new element, but more memory- and query-efficient.
replace_deletedreplaces deleted elements. Note it allows to save memory.- to use it
init_indexshould be called withallow_replace_deleted=True
- to use it
- Thread-safe with other
add_itemscalls, but not withknn_query.
-
mark_deleted(label)- marks the element as deleted, so it will be omitted from search results. Throws an exception if it is already deleted. -
unmark_deleted(label)- unmarks the element as deleted, so it will be not be omitted from search results. -
resize_index(new_size)- changes the maximum capacity of the index. Not thread safe withadd_itemsandknn_query. -
set_ef(ef)- sets the query time accuracy/speed trade-off, defined by theefparameter ( ALGO_PARAMS.md). Note that the parameter is currently not saved along with the index, so you need to set it manually after loading. -
knn_query(data, k = 1, num_threads = -1, filter = None)make a batch query forkclosest elements for each element of thedata(shape:N*dim). Returns a numpy array of (shape:N*k).num_threadssets the number of cpu threads to use (-1 means use default).filterfilters elements by its labels, returns elements with allowed ids. Note that search with a filter works slow in python in multithreaded mode. It is recommended to setnum_threads=1- Thread-safe with other
knn_querycalls, but not withadd_items.
-
load_index(path_to_index, max_elements = 0, allow_replace_deleted = False)loads the index from persistence to the uninitialized index.max_elements(optional) resets the maximum number of elements in the structure.allow_replace_deletedspecifies whether the index being loaded has enabled replacing of deleted elements.
-
save_index(path_to_index)saves the index from persistence. -
set_num_threads(num_threads)set the default number of cpu threads used during data insertion/querying. -
get_items(ids, return_type = 'numpy')- returns a numpy array (shape:N*dim) of vectors that have integer identifiers specified inidsnumpy vector (shape:N) ifreturn_typeislistreturn list of lists. Note that for cosine similarity it currently returns normalized vectors. -
get_ids_list()- returns a list of all elements' ids. -
get_max_elements()- returns the current capacity of the index -
get_current_count()- returns the current number of element stored in the index
Read-only properties of hnswlib.Index class:
-
space- name of the space (can be one of "l2", "ip", or "cosine"). -
dim- dimensionality of the space. -
M- parameter that defines the maximum number of outgoing connections in the graph. -
ef_construction- parameter that controls speed/accuracy trade-off during the index construction. -
max_elements- current capacity of the index. Equivalent top.get_max_elements(). -
element_count- number of items in the index. Equivalent top.get_current_count().
Properties of hnswlib.Index that support reading and writing:
-
ef- parameter controlling query time/accuracy trade-off. -
num_threads- default number of threads to use inadd_itemsorknn_query. Note that callingp.set_num_threads(3)is equivalent top.num_threads=3.
Python bindings examples
- Creating index, inserting elements, searching, serialization/deserialization
- Filtering during the search with a boolean function
- Deleting the elements and reusing the memory of the deleted elements for newly added elements
An example of creating index, inserting elements, searching and pickle serialization:
import hnswlib
import numpy as np
import pickle
dim = 128
num_elements = 10000
# Generating sample data
data = np.float32(np.random.random((num_elements, dim)))
ids = np.arange(num_elements)
# Declaring index
p = hnswlib.Index(space = 'l2', dim = dim) # possible options are l2, cosine or ip
# Initializing index - the maximum number of elements should be known beforehand
p.init_index(max_elements = num_elements, ef_construction = 200, M = 16)
# Element insertion (can be called several times):
p.add_items(data, ids)
# Controlling the recall by setting ef:
p.set_ef(50) # ef should always be > k
# Query dataset, k - number of the closest elements (returns 2 numpy arrays)
labels, distances = p.knn_query(data, k = 1)
# Index objects support pickling
# WARNING: serialization via pickle.dumps(p) or p.__getstate__() is NOT thread-safe with p.add_items method!
# Note: ef parameter is included in serialization; random number generator is initialized with random_seed on Index load
p_copy = pickle.loads(pickle.dumps(p)) # creates a copy of index p using pickle round-trip
### Index parameters are exposed as class properties:
print(f"Parameters passed to constructor: space={p_copy.space}, dim={p_copy.dim}")
print(f"Index construction: M={p_copy.M}, ef_construction={p_copy.ef_construction}")
print(f"Index size is {p_copy.element_count} and index capacity is {p_copy.max_elements}")
print(f"Search speed/quality trade-off parameter: ef={p_copy.ef}")
An example with updates after serialization/deserialization:
import hnswlib
import numpy as np
dim = 16
num_elements = 10000
# Generating sample data
data = np.float32(np.random.random((num_elements, dim)))
# We split the data in two batches:
data1 = data[:num_elements // 2]
data2 = data[num_elements // 2:]
# Declaring index
p = hnswlib.Index(space='l2', dim=dim) # possible options are l2, cosine or ip
# Initializing index
# max_elements - the maximum number of elements (capacity). Will throw an exception if exceeded
# during insertion of an element.
# The capacity can be increased by saving/loading the index, see below.
#
# ef_construction - controls index search speed/build speed tradeoff
#
# M - is tightly connected with internal dimensionality of the data. Strongly affects memory consumption (~M)
# Higher M leads to higher accuracy/run_time at fixed ef/efConstruction
p.init_index(max_elements=num_elements//2, ef_construction=100, M=16)
# Controlling the recall by setting ef:
# higher ef leads to better accuracy, but slower search
p.set_ef(10)
# Set number of threads used during batch search/construction
# By default using all available cores
p.set_num_threads(4)
print("Adding first batch of %d elements" % (len(data1)))
p.add_items(data1)
# Query the elements for themselves and measure recall:
labels, distances = p.knn_query(data1, k=1)
print("Recall for the first batch:", np.mean(labels.reshape(-1) == np.arange(len(data1))), "\n")
# Serializing and deleting the index:
index_path='first_half.bin'
print("Saving index to '%s'" % index_path)
p.save_index("first_half.bin")
del p
# Re-initializing, loading the index
p = hnswlib.Index(space='l2', dim=dim) # the space can be changed - keeps the data, alters the distance function.
print("\nLoading index from 'first_half.bin'\n")
# Increase the total capacity (max_elements), so that it will handle the new data
p.load_index("first_half.bin", max_elements = num_elements)
print("Adding the second batch of %d elements" % (len(data2)))
p.add_items(data2)
# Query the elements for themselves and measure recall:
labels, distances = p.knn_query(data, k=1)
print("Recall for two batches:", np.mean(labels.reshape(-1) == np.arange(len(data))), "\n")
C++ examples
- creating index, inserting elements, searching, serialization/deserialization
- no-exceptions (
*NoExceptions) variants of insert / search / I/O for-fno-exceptionsbuilds - filtering during the search with a boolean function
- deleting the elements and reusing the memory of the deleted elements for newly added elements
- multithreaded usage
- multivector search
- epsilon search
Bindings installation
You can install from sources:
apt-get install -y python-setuptools python-pip
git clone https://github.com/nmslib/hnswlib.git
cd hnswlib
pip install .
or you can install via pip:
pip install hnswlib
For developers
Contributions are highly welcome!
Please make pull requests against the develop branch.
When making changes please run tests (and please add a test to tests/python in case there is new functionality):
python -m unittest discover --start-directory tests/python --pattern "bindings_test*.py"
Other implementations
- Non-metric space library (nmslib) - main library(python, C++), supports exotic distances: https://github.com/nmslib/nmslib
- Faiss library by facebook, uses own HNSW implementation for coarse quantization (python, C++): https://github.com/facebookresearch/faiss
- Code for the paper "Revisiting the Inverted Indices for Billion-Scale Approximate Nearest Neighbors" (current state-of-the-art in compressed indexes, C++): https://github.com/dbaranchuk/ivf-hnsw
- Amazon PECOS https://github.com/amzn/pecos
- TOROS N2 (python, C++): https://github.com/kakao/n2
- Online HNSW (C++): https://github.com/andrusha97/online-hnsw)
- Go implementation: https://github.com/Bithack/go-hnsw
- Python implementation (as a part of the clustering code by by Matteo Dell'Amico): https://github.com/matteodellamico/flexible-clustering
- Julia implmentation https://github.com/JuliaNeighbors/HNSW.jl
- Java implementation: https://github.com/jelmerk/hnswlib
- Java bindings using Java Native Access: https://github.com/stepstone-tech/hnswlib-jna
- .Net implementation: https://github.com/curiosity-ai/hnsw-sharp
- CUDA implementation: https://github.com/js1010/cuhnsw
- Rust implementation https://github.com/rust-cv/hnsw
- Rust implementation for memory and thread safety purposes and There is 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.: https://github.com/jean-pierreBoth/hnswlib-rs
200M SIFT test reproduction
To download and extract the bigann dataset (from root directory):
python tests/cpp/download_bigann.py
To compile:
mkdir build
cd build
cmake ..
make all
To run the test on 200M SIFT subset:
./main
The size of the BigANN subset (in millions) is controlled by the variable subset_size_millions hardcoded in sift_1b.cpp.
Updates test
To generate testing data (from root directory):
cd tests/cpp
python update_gen_data.py
To compile (from root directory):
mkdir build
cd build
cmake ..
make
To run test without updates (from build directory)
./test_updates
To run test with updates (from build directory)
./test_updates update
HNSW example demos
- Visual search engine for 1M amazon products (MXNet + HNSW): website, code, demo by @ThomasDelteil
References
HNSW paper:
@article{malkov2018efficient,
title={Efficient and robust approximate nearest neighbor search using hierarchical navigable small world graphs},
author={Malkov, Yu A and Yashunin, Dmitry A},
journal={IEEE transactions on pattern analysis and machine intelligence},
volume={42},
number={4},
pages={824--836},
year={2018},
publisher={IEEE}
}
The update algorithm supported in this repository is to be published in "Dynamic Updates For HNSW, Hierarchical Navigable Small World Graphs" US Patent 15/929,802 by Apoorv Sharma, Abhishek Tayal and Yury Malkov.