hnswlib
hnswlib 是一款专为快速近似最近邻搜索设计的轻量级开源库,核心基于高效的 HNSW 算法。它主要解决了在海量高维数据中,如何以极低的计算成本和内存占用,迅速找到与目标最相似数据的难题,广泛应用于推荐系统、图像检索及自然语言处理等领域。
这款工具特别适合需要高性能向量检索能力的开发者与研究人员。其最大亮点在于“仅头文件”的 C++ 实现,无需复杂依赖即可集成,同时提供了便捷的 Python 接口,并支持 Java 和 R 语言调用。hnswlib 支持动态增量构建索引,允许随时插入、更新甚至标记删除数据元素,无需重建整个索引,极大地提升了工程灵活性。此外,它还支持自定义距离度量(如欧氏距离、内积、余弦距离),并在多线程搜索场景下进行了深度优化,显著减少了内存足迹并加快了构建速度。无论是构建原型还是部署生产级服务,hnswlib 都能以简洁的代码结构提供卓越的检索性能。
使用场景
某电商平台的推荐系统团队需要在毫秒级内,从千万级商品向量库中为用户实时检索最相似的“猜你喜欢”商品。
没有 hnswlib 时
- 响应延迟极高:采用暴力穷举或传统索引算法,面对千万级数据时单次查询耗时超过数百毫秒,导致用户刷新页面时出现明显卡顿。
- 内存资源爆炸:全量加载高精度索引占用数十 GB 内存,迫使团队不得不升级昂贵的服务器集群以维持服务。
- 数据更新困难:商品上下架或价格变动导致向量特征改变时,重建索引需要停机维护数小时,无法支持业务所需的实时性。
- 开发集成复杂:引入重型依赖库增加了环境配置难度,且缺乏灵活的 Python 接口,阻碍了算法工程师的快速迭代验证。
使用 hnswlib 后
- 查询速度飞跃:利用 HNSW 图的近似最近邻搜索特性,将千万级数据的检索时间压缩至 5 毫秒以内,实现丝滑的实时推荐体验。
- 内存大幅优化:凭借轻量级的头文件设计和高效的图结构,内存占用降低约 60%,直接在现有普通服务器上即可流畅运行。
- 支持动态增删:借助其增量构建和元素标记删除功能,新商品向量可即时插入索引,旧商品可在线标记替换,无需重启服务。
- 无缝 Python 集成:通过简洁的 Python 绑定直接调用 C++ 核心性能,算法团队能快速原型开发并轻松部署到生产环境。
hnswlib 通过极致的速度与低资源消耗,让大规模向量实时检索从“昂贵的基础设施挑战”变成了“开箱即用的标准能力”。
运行环境要求
- Linux
- macOS
- Windows
未说明
未说明

快速开始
Hnswlib - 快速近似最近邻搜索
仅头文件的 C++ HNSW 实现,带有 Python 绑定、插入和更新功能。
新闻:
版本 0.9.0
- 修复了带过滤器的暴力搜索结果不正确的问题 (#514),由 @lukaszsmolinski 完成
- 修复了 BFIndex 中缺失的归一化检查 (#514),由 @lukaszsmolinski 完成
- 当可用元素少于 k 个时抛出异常 (#514),由 @lukaszsmolinski 完成
- 移除未使用的变量 (#531),由 @lulyon 完成
- 将 README 中的余弦相似度改为距离,由 @yurymalkov 完成
版本 0.8.0
- 多向量文档搜索和 epsilon 搜索(目前仅在 C++ 中支持)
- 默认情况下不再进行统计聚合,这加快了多线程搜索的速度(似乎也确实很少有人使用:Issue #495)。
- 各种错误修复和改进
get_items现在增加了return_type参数,可选值为 'numpy' 或 'list'
完整变更列表:https://github.com/nmslib/hnswlib/pull/523
版本 0.7.0
- 增加了对过滤的支持 (#402, #430),由 @kishorenc 完成
- 添加了用于过滤的 Python 接口(但请注意,其性能受限于 GIL)(#417),由 @gtsoukas 完成
- 支持用新插入的元素替换已被标记为删除的元素,以控制索引大小 (#418),由 @dyashuni 完成
- 修复了更新/插入操作中的数据竞争/死锁问题,并添加了多线程操作的压力测试 (#418),由 @dyashuni 完成
- 文档、测试、异常处理、重构工作 (#375, #379, #380, #395, #396, #401, #406, #404, #409, #410, #416, #415, #431, #432, #433),由 @jlmelville、@dyashuni、@kishorenc、@korzhenevski、@yoshoku、@jianshu93、@PLNech 等完成
- 全局链接 (#383),由 @MasterAler 完成;MSVC 中使用 USE_SSE (#408),由 @alxvth 完成
亮点:
- 轻量级、仅头文件实现,除 C++11 外无其他依赖。
- 提供 C++ 和 Python 接口,并对外支持 Java 和 R(https://github.com/jlmelville/rcpphnsw)。
- 完全支持增量式索引构建和元素更新(感谢 Apoorv Sharma 的贡献)。同时支持元素删除操作(通过在索引中标记删除,后续可用其他元素替换)。Python 版本的索引可以被 pickle 序列化。
- 可与用户自定义的距离函数一起使用(C++)。
- 相较于当前 nmslib 的实现,内存占用显著降低,构建时间更快。
算法参数的说明请参阅 ALGO_PARAMS.md。
Python 绑定
支持的距离度量:
| 距离 | 参数 | 公式 |
|---|---|---|
| 平方 L2 | 'l2' | d = sum((Ai-Bi)^2) |
| 内积 | 'ip' | d = 1.0 - sum(Ai*Bi) |
| 余弦距离 | 'cosine' | d = 1.0 - sum(Ai*Bi) / sqrt(sum(Ai*Ai) * sum(Bi*Bi)) |
请注意,内积并不是一个真正的度量。某个元素可能比它自身更接近另一个元素。如果从索引中移除所有不与自身最近的元素,这可以带来一定的性能提升。
对于其他空间,请使用 nmslib 库:https://github.com/nmslib/nmslib。
API 描述
hnswlib.Index(space, dim)在空间space中创建一个未初始化的 HNSW 索引,维度为整数dim。
hnswlib.Index 方法:
init_index(max_elements, M = 16, ef_construction = 200, random_seed = 100, allow_replace_deleted = False)初始化一个空的索引。max_elements定义了结构中可存储的最大元素数量(可以增加或减少)。ef_construction定义了构建时间与准确性之间的权衡(参见 ALGO_PARAMS.md)。M定义了图中每个节点的最大出边数(参见 ALGO_PARAMS.md)。allow_replace_deleted允许用新添加的元素替换已删除的元素。
add_items(data, ids, num_threads = -1, replace_deleted = False)将data(形状为N*dim的向量 NumPy 数组)插入到结构中。num_threads设置使用的 CPU 线程数(-1 表示使用默认值)。ids是可选的 N 大小的整数标签 NumPy 数组,用于data中的所有元素。- 如果索引中已经存在具有相同标签的元素,其特征将被更新。请注意,更新操作比插入新元素慢,但更节省内存且查询效率更高。
replace_deleted会替换已删除的元素。注意,这有助于节省内存。- 要使用此功能,必须在调用
init_index时设置allow_replace_deleted=True。
- 要使用此功能,必须在调用
- 该方法与其他
add_items调用是线程安全的,但与knn_query不是。
mark_deleted(label)标记元素为已删除,使其不会出现在搜索结果中。如果元素已被标记为已删除,则会抛出异常。unmark_deleted(label)取消标记已删除的元素,使其会出现在搜索结果中。resize_index(new_size)更改索引的最大容量。与add_items和knn_query不是线程安全的。set_ef(ef)设置查询时间、准确性和速度之间的权衡,由ef参数定义(ALGO_PARAMS.md)。请注意,该参数目前不会与索引一起保存,因此在加载后需要手动设置。knn_query(data, k = 1, num_threads = -1, filter = None)对data中的每个元素进行批量查询,返回每个元素最接近的k个元素。data(形状为N*dim)。返回形状为N*k的 NumPy 数组。num_threads设置使用的 CPU 线程数(-1 表示使用默认值)。filter按标签过滤元素,只返回具有允许 ID 的元素。请注意,在多线程模式下,使用过滤器进行搜索的速度较慢。建议将num_threads设置为 1。- 该方法与其他
knn_query调用是线程安全的,但与add_items不是。
load_index(path_to_index, max_elements = 0, allow_replace_deleted = False)从持久化存储中加载索引到未初始化的索引中。max_elements(可选)重置结构中的最大元素数量。allow_replace_deleted指定正在加载的索引是否启用了替换已删除元素的功能。
save_index(path_to_index)将索引保存到持久化存储中。set_num_threads(num_threads)设置数据插入/查询过程中默认使用的 CPU 线程数。get_items(ids, return_type = 'numpy')返回形状为N*dim的向量 NumPy 数组,这些向量的整数标识符在idsNumPy 向量中指定(形状为N)。如果return_type为list,则返回列表的列表。请注意,对于余弦相似度,当前返回的是归一化向量。get_ids_list()返回所有元素的 ID 列表。get_max_elements()返回索引的当前容量。get_current_count()返回索引中当前存储的元素数量。
hnswlib.Index 类的只读属性:
space- 空间名称(可以是 "l2"、"ip" 或 "cosine")。dim- 空间的维数。M- 定义图中每个节点最大出边数的参数。ef_construction- 控制索引构建过程中速度与准确性之间权衡的参数。max_elements- 索引的当前容量。等同于p.get_max_elements()。element_count- 索引中的项目数量。等同于p.get_current_count()。
hnswlib.Index 支持读写的属性:
ef- 控制查询时间与准确性之间权衡的参数。num_threads- 在add_items或knn_query中默认使用的线程数。请注意,调用p.set_num_threads(3)等同于p.num_threads=3。
Python 绑定示例
- 创建索引、插入元素、搜索、序列化/反序列化
- 使用布尔函数在搜索过程中进行过滤
- 删除元素并重新利用已删除元素的内存来存储新添加的元素
创建索引、插入元素、搜索和 pickle 序列化的示例:
import hnswlib
import numpy as np
import pickle
dim = 128
num_elements = 10000
# 生成示例数据
data = np.float32(np.random.random((num_elements, dim)))
ids = np.arange(num_elements)
# 声明索引
p = hnswlib.Index(space = 'l2', dim = dim) # 可能的选项是 l2、cosine 或 ip
# 初始化索引 - 必须事先知道最大元素数量
p.init_index(max_elements = num_elements, ef_construction = 200, M = 16)
# 插入元素(可以多次调用):
p.add_items(data, ids)
# 通过设置 ef 来控制召回率:
p.set_ef(50) # ef 值应始终大于 k
# 查询数据集,k 表示最接近的元素数量(返回两个 NumPy 数组)
labels, distances = p.knn_query(data, k = 1)
# 索引对象支持 pickle 序列化
# 注意:使用 pickle.dumps(p) 或 p.__getstate__() 进行序列化与 p.add_items 方法不线程安全!
# 注意:ef 参数包含在序列化中;随机数生成器在加载索引时使用 random_seed 进行初始化
p_copy = pickle.loads(pickle.dumps(p)) # 使用 pickle 循环创建索引 p 的副本
### 索引参数作为类属性公开:
print(f"传递给构造函数的参数:space={p_copy.space}, dim={p_copy.dim}")
print(f"索引构建:M={p_copy.M}, ef_construction={p_copy.ef_construction}")
print(f"索引大小为 {p_copy.element_count},索引容量为 {p_copy.max_elements}")
print(f"搜索速度与质量的权衡参数:ef={p_copy.ef}")
序列化/反序列化后的更新示例:
import hnswlib
import numpy as np
dim = 16
num_elements = 10000
# 生成示例数据
data = np.float32(np.random.random((num_elements, dim)))
# 我们将数据分为两个批次:
data1 = data[:num_elements // 2]
data2 = data[num_elements // 2:]
# 声明索引
p = hnswlib.Index(space='l2', dim=dim) # 可选值为 l2、cosine 或 ip
# 初始化索引
# max_elements - 最大元素数量(容量)。如果在插入元素时超出此限制,将会抛出异常。
# 容量可以通过保存/加载索引来增加,详见下文。
#
# ef_construction - 控制索引构建速度与查询速度之间的权衡。
#
# M - 与数据的内部维度紧密相关。强烈影响内存消耗(约与 M 成正比)。
# 较高的 M 值可在固定的 ef/efConstruction 下提高检索准确性和运行时间。
p.init_index(max_elements=num_elements//2, ef_construction=100, M=16)
# 通过设置 ef 来控制召回率:
# 较高的 ef 值可提高准确性,但会降低查询速度。
p.set_ef(10)
# 设置批量搜索/构建过程中使用的线程数
# 默认使用所有可用核心
p.set_num_threads(4)
print("添加第一批次 %d 个元素" % (len(data1)))
p.add_items(data1)
# 查询这些元素自身并测量召回率:
labels, distances = p.knn_query(data1, k=1)
print("第一批次的召回率:", np.mean(labels.reshape(-1) == np.arange(len(data1))), "\n")
# 序列化并删除索引:
index_path='first_half.bin'
print("将索引保存到 '%s'" % index_path)
p.save_index("first_half.bin")
del p
# 重新初始化并加载索引
p = hnswlib.Index(space='l2', dim=dim) # 空间可以更改——这会保留数据,但改变距离函数。
print("\n从 'first_half.bin' 加载索引\n")
# 增加总容量(max_elements),以便容纳新数据
p.load_index("first_half.bin", max_elements = num_elements)
print("添加第二批次 %d 个元素" % (len(data2)))
p.add_items(data2)
# 查询这些元素自身并测量召回率:
labels, distances = p.knn_query(data, k=1)
print("两批次的召回率:", np.mean(labels.reshape(-1) == np.arange(len(data))), "\n")
C++ 示例
- 创建索引、插入元素、搜索、序列化/反序列化
- 在搜索过程中使用布尔函数进行过滤
- 删除元素并将已删除元素的内存空间重新用于新插入的元素
- 多线程使用
- 多向量搜索
- ε 搜索
绑定库安装
您可以从源代码安装:
apt-get install -y python-setuptools python-pip
git clone https://github.com/nmslib/hnswlib.git
cd hnswlib
pip install .
或者您也可以通过 pip 安装:
pip install hnswlib
针对开发者
我们非常欢迎贡献!
请针对 develop 分支提交拉取请求。
在进行更改时,请运行测试(如果有新功能,请在 tests/python 中添加测试):
python -m unittest discover --start-directory tests/python --pattern "bindings_test*.py"
其他实现
- 非度量空间库(nmslib)——主库(Python、C++),支持多种特殊距离:https://github.com/nmslib/nmslib
- Facebook 的 Faiss 库,使用其自有的 HNSW 实现进行粗量化处理(Python、C++): https://github.com/facebookresearch/faiss
- 论文“重访十亿级近似最近邻的倒排索引”中的代码 (目前压缩索引领域的最先进方法,C++): https://github.com/dbaranchuk/ivf-hnsw
- Amazon PECOS https://github.com/amzn/pecos
- TOROS N2(Python、C++):https://github.com/kakao/n2
- 在线 HNSW(C++):https://github.com/andrusha97/online-hnsw)
- Go 实现:https://github.com/Bithack/go-hnsw
- Python 实现(作为 Matteo Dell'Amico 聚类代码的一部分):https://github.com/matteodellamico/flexible-clustering
- Julia 实现 https://github.com/JuliaNeighbors/HNSW.jl
- Java 实现:https://github.com/jelmerk/hnswlib
- 使用 Java Native Access 的 Java 绑定:https://github.com/stepstone-tech/hnswlib-jna
- .Net 实现:https://github.com/curiosity-ai/hnsw-sharp
- CUDA 实现:https://github.com/js1010/cuhnsw
- Rust 实现 https://github.com/rust-cv/hnsw
- Rust 实现注重内存和线程安全,并提供一个 Trait 接口,允许用户实现自己的距离函数。该接口接受类型为 T 的切片,其中 T 需满足 Serialize+Clone+Send+Sync 的要求:https://github.com/jean-pierreBoth/hnswlib-rs
2 亿 SIFT 数据集复现
从根目录下载并解压 bigann 数据集:
python tests/cpp/download_bigann.py
编译步骤:
mkdir build
cd build
cmake ..
make all
运行 2 亿 SIFT 子集测试:
./main
BigANN 子集的大小(以百万为单位)由 sift_1b.cpp 文件中硬编码的变量 subset_size_millions 控制。
更新测试
从根目录生成测试数据:
cd tests/cpp
python update_gen_data.py
编译步骤(从根目录开始):
mkdir build
cd build
cmake ..
make
运行不包含更新的测试(从 build 目录):
./test_updates
运行包含更新的测试(从 build 目录):
./test_updates update
HNSW 示例演示
- 用于 100 万件亚马逊商品的视觉搜索引擎(MXNet + HNSW):网站,代码,演示者:@ThomasDelteil
参考文献
HNSW 论文:
@article{malkov2018efficient,
title={高效且鲁棒的近似最近邻搜索:基于分层可导航小世界图},
author={Malkov, Yu A 和 Yashunin, Dmitry A},
journal={IEEE 模式分析与机器智能汇刊},
volume={42},
number={4},
pages={824--836},
year={2018},
publisher={IEEE}
}
本仓库支持的更新算法即将发表于“Apoorv Sharma、Abhishek Tayal 和 Yury Malkov 提交的 US Patent 15/929,802:HNSW 的动态更新,分层可导航小世界图”。
版本历史
v0.9.02026/03/28v0.8.02023/12/03v0.7.02023/02/07v0.6.22022/02/14v0.6.12022/02/06v0.6.02021/12/09v0.5.22021/06/30v0.5.02021/01/29v0.4.02020/06/27v0.3.42019/12/16常见问题
相似工具推荐
ML-For-Beginners
ML-For-Beginners 是由微软推出的一套系统化机器学习入门课程,旨在帮助零基础用户轻松掌握经典机器学习知识。这套课程将学习路径规划为 12 周,包含 26 节精炼课程和 52 道配套测验,内容涵盖从基础概念到实际应用的完整流程,有效解决了初学者面对庞大知识体系时无从下手、缺乏结构化指导的痛点。 无论是希望转型的开发者、需要补充算法背景的研究人员,还是对人工智能充满好奇的普通爱好者,都能从中受益。课程不仅提供了清晰的理论讲解,还强调动手实践,让用户在循序渐进中建立扎实的技能基础。其独特的亮点在于强大的多语言支持,通过自动化机制提供了包括简体中文在内的 50 多种语言版本,极大地降低了全球不同背景用户的学习门槛。此外,项目采用开源协作模式,社区活跃且内容持续更新,确保学习者能获取前沿且准确的技术资讯。如果你正寻找一条清晰、友好且专业的机器学习入门之路,ML-For-Beginners 将是理想的起点。
scikit-learn
scikit-learn 是一个基于 Python 构建的开源机器学习库,依托于 SciPy、NumPy 等科学计算生态,旨在让机器学习变得简单高效。它提供了一套统一且简洁的接口,涵盖了从数据预处理、特征工程到模型训练、评估及选择的全流程工具,内置了包括线性回归、支持向量机、随机森林、聚类等在内的丰富经典算法。 对于希望快速验证想法或构建原型的数据科学家、研究人员以及 Python 开发者而言,scikit-learn 是不可或缺的基础设施。它有效解决了机器学习入门门槛高、算法实现复杂以及不同模型间调用方式不统一的痛点,让用户无需重复造轮子,只需几行代码即可调用成熟的算法解决分类、回归、聚类等实际问题。 其核心技术亮点在于高度一致的 API 设计风格,所有估算器(Estimator)均遵循相同的调用逻辑,极大地降低了学习成本并提升了代码的可读性与可维护性。此外,它还提供了强大的模型选择与评估工具,如交叉验证和网格搜索,帮助用户系统地优化模型性能。作为一个由全球志愿者共同维护的成熟项目,scikit-learn 以其稳定性、详尽的文档和活跃的社区支持,成为连接理论学习与工业级应用的最
keras
Keras 是一个专为人类设计的深度学习框架,旨在让构建和训练神经网络变得简单直观。它解决了开发者在不同深度学习后端之间切换困难、模型开发效率低以及难以兼顾调试便捷性与运行性能的痛点。 无论是刚入门的学生、专注算法的研究人员,还是需要快速落地产品的工程师,都能通过 Keras 轻松上手。它支持计算机视觉、自然语言处理、音频分析及时间序列预测等多种任务。 Keras 3 的核心亮点在于其独特的“多后端”架构。用户只需编写一套代码,即可灵活选择 TensorFlow、JAX、PyTorch 或 OpenVINO 作为底层运行引擎。这一特性不仅保留了 Keras 一贯的高层易用性,还允许开发者根据需求自由选择:利用 JAX 或 PyTorch 的即时执行模式进行高效调试,或切换至速度最快的后端以获得最高 350% 的性能提升。此外,Keras 具备强大的扩展能力,能无缝从本地笔记本电脑扩展至大规模 GPU 或 TPU 集群,是连接原型开发与生产部署的理想桥梁。
crawl4ai
Crawl4AI 是一款专为大语言模型(LLM)设计的开源网络爬虫与数据提取工具。它的核心使命是将纷繁复杂的网页内容转化为干净、结构化的 Markdown 格式,直接服务于检索增强生成(RAG)、智能体构建及各类数据管道,让 AI 能更轻松地“读懂”互联网。 传统爬虫往往面临反爬机制拦截、动态内容加载困难以及输出格式杂乱等痛点,导致后续数据处理成本高昂。Crawl4AI 通过内置自动化的三级反机器人检测、代理升级策略以及对 Shadow DOM 的深度支持,有效突破了这些障碍。它能智能移除同意弹窗,处理深层链接,并具备长任务崩溃恢复能力,确保数据采集的稳定与高效。 这款工具特别适合开发者、AI 研究人员及数据工程师使用。无论是需要为本地模型构建知识库,还是搭建大规模自动化信息采集流程,Crawl4AI 都提供了极高的可控性与灵活性。作为 GitHub 上备受瞩目的开源项目,它完全免费开放,无需繁琐的注册或昂贵的 API 费用,让用户能够专注于数据价值本身而非采集难题。
meilisearch
Meilisearch 是一个开源的极速搜索服务,专为现代应用和网站打造,开箱即用。它能帮助开发者快速集成高质量的搜索功能,无需复杂的配置或额外的数据预处理。传统搜索方案往往需要大量调优才能实现准确结果,而 Meilisearch 内置了拼写容错、同义词识别、即时响应等实用特性,并支持 AI 驱动的混合搜索(结合关键词与语义理解),显著提升用户查找信息的体验。 Meilisearch 特别适合 Web 开发者、产品团队或初创公司使用,尤其适用于需要快速上线搜索功能的场景,如电商网站、内容平台或 SaaS 应用。它提供简洁的 RESTful API 和多种语言 SDK,部署简单,资源占用低,本地开发或生产环境均可轻松运行。对于希望在不依赖大型云服务的前提下,为用户提供流畅、智能搜索体验的团队来说,Meilisearch 是一个高效且友好的选择。
Made-With-ML
Made-With-ML 是一个面向实战的开源项目,旨在帮助开发者系统掌握从设计、开发到部署和迭代生产级机器学习应用的完整流程。它解决了许多人在学习机器学习时“会训练模型但不会上线”的痛点,强调将软件工程最佳实践与 ML 技术结合,构建可靠、可维护的端到端系统。 该项目特别适合三类人群:一是希望将模型真正落地的开发者(包括软件工程师、数据科学家);二是刚毕业、想补齐工业界所需技能的学生;三是需要理解技术边界以更好推动产品的技术管理者或产品经理。 Made-With-ML 的亮点在于注重第一性原理讲解,避免盲目调包;同时覆盖 MLOps 关键环节(如实验跟踪、模型测试、服务部署、CI/CD 等),并支持在 Python 生态内平滑扩展训练与推理任务,无需切换语言或复杂基础设施。课程内容结构清晰,配有详细代码示例和视频导览,兼顾理论深度与工程实用性。