hnswlib

GitHub
5.2k 807 非常简单 1 次阅读 3天前Apache-2.0数据工具
AI 解读 由 AI 自动生成,仅供参考

hnswlib 是一款专为快速近似最近邻搜索设计的轻量级开源库,核心基于高效的 HNSW 算法。它主要解决了在海量高维数据中,如何以极低的计算成本和内存占用,迅速找到与目标最相似数据的难题,广泛应用于推荐系统、图像检索及自然语言处理等领域。

这款工具特别适合需要高性能向量检索能力的开发者与研究人员。其最大亮点在于“仅头文件”的 C++ 实现,无需复杂依赖即可集成,同时提供了便捷的 Python 接口,并支持 Java 和 R 语言调用。hnswlib 支持动态增量构建索引,允许随时插入、更新甚至标记删除数据元素,无需重建整个索引,极大地提升了工程灵活性。此外,它还支持自定义距离度量(如欧氏距离、内积、余弦距离),并在多线程搜索场景下进行了深度优化,显著减少了内存足迹并加快了构建速度。无论是构建原型还是部署生产级服务,hnswlib 都能以简洁的代码结构提供卓越的检索性能。

使用场景

某电商平台的推荐系统团队需要在毫秒级内,从千万级商品向量库中为用户实时检索最相似的“猜你喜欢”商品。

没有 hnswlib 时

  • 响应延迟极高:采用暴力穷举或传统索引算法,面对千万级数据时单次查询耗时超过数百毫秒,导致用户刷新页面时出现明显卡顿。
  • 内存资源爆炸:全量加载高精度索引占用数十 GB 内存,迫使团队不得不升级昂贵的服务器集群以维持服务。
  • 数据更新困难:商品上下架或价格变动导致向量特征改变时,重建索引需要停机维护数小时,无法支持业务所需的实时性。
  • 开发集成复杂:引入重型依赖库增加了环境配置难度,且缺乏灵活的 Python 接口,阻碍了算法工程师的快速迭代验证。

使用 hnswlib 后

  • 查询速度飞跃:利用 HNSW 图的近似最近邻搜索特性,将千万级数据的检索时间压缩至 5 毫秒以内,实现丝滑的实时推荐体验。
  • 内存大幅优化:凭借轻量级的头文件设计和高效的图结构,内存占用降低约 60%,直接在现有普通服务器上即可流畅运行。
  • 支持动态增删:借助其增量构建和元素标记删除功能,新商品向量可即时插入索引,旧商品可在线标记替换,无需重启服务。
  • 无缝 Python 集成:通过简洁的 Python 绑定直接调用 C++ 核心性能,算法团队能快速原型开发并轻松部署到生产环境。

hnswlib 通过极致的速度与低资源消耗,让大规模向量实时检索从“昂贵的基础设施挑战”变成了“开箱即用的标准能力”。

运行环境要求

操作系统
  • Linux
  • macOS
  • Windows
GPU

未说明

内存

未说明

依赖
notes该工具为仅头文件(header-only)的 C++ 实现,除 C++11 标准外无其他外部依赖。支持通过 Python 绑定使用,利用 GIL 限制可能在多线程过滤搜索时影响性能。支持增量索引构建、元素更新及逻辑删除。在 Windows 上使用 MSVC 编译时需注意 SSE 指令集的支持配置。
python未说明
numpy
C++11 编译器
hnswlib hero image

快速开始

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 完成

亮点:

  1. 轻量级、仅头文件实现,除 C++11 外无其他依赖。
  2. 提供 C++ 和 Python 接口,并对外支持 Java 和 R(https://github.com/jlmelville/rcpphnsw)。
  3. 完全支持增量式索引构建和元素更新(感谢 Apoorv Sharma 的贡献)。同时支持元素删除操作(通过在索引中标记删除,后续可用其他元素替换)。Python 版本的索引可以被 pickle 序列化。
  4. 可与用户自定义的距离函数一起使用(C++)。
  5. 相较于当前 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_itemsknn_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 数组,这些向量的整数标识符在 ids NumPy 向量中指定(形状为 N)。如果 return_typelist,则返回列表的列表。请注意,对于余弦相似度,当前返回的是归一化向量。

  • 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_itemsknn_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"

其他实现

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 示例演示

参考文献

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/28
v0.8.02023/12/03
v0.7.02023/02/07
v0.6.22022/02/14
v0.6.12022/02/06
v0.6.02021/12/09
v0.5.22021/06/30
v0.5.02021/01/29
v0.4.02020/06/27
v0.3.42019/12/16

常见问题

相似工具推荐

ML-For-Beginners

ML-For-Beginners 是由微软推出的一套系统化机器学习入门课程,旨在帮助零基础用户轻松掌握经典机器学习知识。这套课程将学习路径规划为 12 周,包含 26 节精炼课程和 52 道配套测验,内容涵盖从基础概念到实际应用的完整流程,有效解决了初学者面对庞大知识体系时无从下手、缺乏结构化指导的痛点。 无论是希望转型的开发者、需要补充算法背景的研究人员,还是对人工智能充满好奇的普通爱好者,都能从中受益。课程不仅提供了清晰的理论讲解,还强调动手实践,让用户在循序渐进中建立扎实的技能基础。其独特的亮点在于强大的多语言支持,通过自动化机制提供了包括简体中文在内的 50 多种语言版本,极大地降低了全球不同背景用户的学习门槛。此外,项目采用开源协作模式,社区活跃且内容持续更新,确保学习者能获取前沿且准确的技术资讯。如果你正寻找一条清晰、友好且专业的机器学习入门之路,ML-For-Beginners 将是理想的起点。

85k|★★☆☆☆|今天
图像数据工具视频

scikit-learn

scikit-learn 是一个基于 Python 构建的开源机器学习库,依托于 SciPy、NumPy 等科学计算生态,旨在让机器学习变得简单高效。它提供了一套统一且简洁的接口,涵盖了从数据预处理、特征工程到模型训练、评估及选择的全流程工具,内置了包括线性回归、支持向量机、随机森林、聚类等在内的丰富经典算法。 对于希望快速验证想法或构建原型的数据科学家、研究人员以及 Python 开发者而言,scikit-learn 是不可或缺的基础设施。它有效解决了机器学习入门门槛高、算法实现复杂以及不同模型间调用方式不统一的痛点,让用户无需重复造轮子,只需几行代码即可调用成熟的算法解决分类、回归、聚类等实际问题。 其核心技术亮点在于高度一致的 API 设计风格,所有估算器(Estimator)均遵循相同的调用逻辑,极大地降低了学习成本并提升了代码的可读性与可维护性。此外,它还提供了强大的模型选择与评估工具,如交叉验证和网格搜索,帮助用户系统地优化模型性能。作为一个由全球志愿者共同维护的成熟项目,scikit-learn 以其稳定性、详尽的文档和活跃的社区支持,成为连接理论学习与工业级应用的最

65.6k|★☆☆☆☆|今天
开发框架其他数据工具

keras

Keras 是一个专为人类设计的深度学习框架,旨在让构建和训练神经网络变得简单直观。它解决了开发者在不同深度学习后端之间切换困难、模型开发效率低以及难以兼顾调试便捷性与运行性能的痛点。 无论是刚入门的学生、专注算法的研究人员,还是需要快速落地产品的工程师,都能通过 Keras 轻松上手。它支持计算机视觉、自然语言处理、音频分析及时间序列预测等多种任务。 Keras 3 的核心亮点在于其独特的“多后端”架构。用户只需编写一套代码,即可灵活选择 TensorFlow、JAX、PyTorch 或 OpenVINO 作为底层运行引擎。这一特性不仅保留了 Keras 一贯的高层易用性,还允许开发者根据需求自由选择:利用 JAX 或 PyTorch 的即时执行模式进行高效调试,或切换至速度最快的后端以获得最高 350% 的性能提升。此外,Keras 具备强大的扩展能力,能无缝从本地笔记本电脑扩展至大规模 GPU 或 TPU 集群,是连接原型开发与生产部署的理想桥梁。

63.9k|★★☆☆☆|昨天
开发框架数据工具其他

crawl4ai

Crawl4AI 是一款专为大语言模型(LLM)设计的开源网络爬虫与数据提取工具。它的核心使命是将纷繁复杂的网页内容转化为干净、结构化的 Markdown 格式,直接服务于检索增强生成(RAG)、智能体构建及各类数据管道,让 AI 能更轻松地“读懂”互联网。 传统爬虫往往面临反爬机制拦截、动态内容加载困难以及输出格式杂乱等痛点,导致后续数据处理成本高昂。Crawl4AI 通过内置自动化的三级反机器人检测、代理升级策略以及对 Shadow DOM 的深度支持,有效突破了这些障碍。它能智能移除同意弹窗,处理深层链接,并具备长任务崩溃恢复能力,确保数据采集的稳定与高效。 这款工具特别适合开发者、AI 研究人员及数据工程师使用。无论是需要为本地模型构建知识库,还是搭建大规模自动化信息采集流程,Crawl4AI 都提供了极高的可控性与灵活性。作为 GitHub 上备受瞩目的开源项目,它完全免费开放,无需繁琐的注册或昂贵的 API 费用,让用户能够专注于数据价值本身而非采集难题。

63.2k|★★☆☆☆|3天前
数据工具Agent

meilisearch

Meilisearch 是一个开源的极速搜索服务,专为现代应用和网站打造,开箱即用。它能帮助开发者快速集成高质量的搜索功能,无需复杂的配置或额外的数据预处理。传统搜索方案往往需要大量调优才能实现准确结果,而 Meilisearch 内置了拼写容错、同义词识别、即时响应等实用特性,并支持 AI 驱动的混合搜索(结合关键词与语义理解),显著提升用户查找信息的体验。 Meilisearch 特别适合 Web 开发者、产品团队或初创公司使用,尤其适用于需要快速上线搜索功能的场景,如电商网站、内容平台或 SaaS 应用。它提供简洁的 RESTful API 和多种语言 SDK,部署简单,资源占用低,本地开发或生产环境均可轻松运行。对于希望在不依赖大型云服务的前提下,为用户提供流畅、智能搜索体验的团队来说,Meilisearch 是一个高效且友好的选择。

57k|★★☆☆☆|今天
图像Agent数据工具

Made-With-ML

Made-With-ML 是一个面向实战的开源项目,旨在帮助开发者系统掌握从设计、开发到部署和迭代生产级机器学习应用的完整流程。它解决了许多人在学习机器学习时“会训练模型但不会上线”的痛点,强调将软件工程最佳实践与 ML 技术结合,构建可靠、可维护的端到端系统。 该项目特别适合三类人群:一是希望将模型真正落地的开发者(包括软件工程师、数据科学家);二是刚毕业、想补齐工业界所需技能的学生;三是需要理解技术边界以更好推动产品的技术管理者或产品经理。 Made-With-ML 的亮点在于注重第一性原理讲解,避免盲目调包;同时覆盖 MLOps 关键环节(如实验跟踪、模型测试、服务部署、CI/CD 等),并支持在 Python 生态内平滑扩展训练与推理任务,无需切换语言或复杂基础设施。课程内容结构清晰,配有详细代码示例和视频导览,兼顾理论深度与工程实用性。

47.1k|★★☆☆☆|今天
语言模型其他数据工具