开发者
我要评分
获取效率
正确性
完整性
易理解
在线提单
论坛求助

使用示例

本节介绍优化后hnswlib的测试使用示例。包括快速上手示例、性能测试和功能测试的使用示例,并且提供性能调优参数指导。

前提条件

请参见编译hnswlib完成hnswlib的编译。

目录说明

假设程序运行的目录为“/path/to/hnswlib”,完整的目录结构应如下所示:
├── configs            // 数据集相关配置文件
├── hnswlib            // hnswlib头文件
├── python_bindings    // python接口文件
├── include            // 性能测试文件
├── src                // 性能测试头文件
├── examples           // 功能测试文件
├── tests              // 功能测试文件
├── test               // 测试文件,包含ut测试和fuzz测试
└── ALGO_PARAMS.md
└── CMakeLists.txt     // 功能测试CMakeLists.txt文件
└── LICENSE
└── Makefile           // 性能测试Makefile文件
└── MANIFEST.in
└── pyproject.toml
└── README.en.md
└── README.md
└── run.sh             // 编译并执行功能测试的脚本
└── test.sh            // 执行性能测试的脚本
└── setup.py
└── TESTING_RECALL.md

快速上手示例

以下提供一个创建索引、插入元素、搜索、保存索引和加载索引的示例。
#include "hnswlib/hnswlib.h"

int main() {
    int dim = 128;
    int M = 16;
    int efc = 200;
    int efs = 30;
    int k = 10;
    int num_points = 1000;
    int num_queries = 100;
    string distance_type = "l2";
    string data_type = "fp16";

    // 生成FP32类型测试数据
    std::default_random_engine engine(5678);
    std::uniform_real_distribution<float> distribution(0.0f, 1.0f);
    std::vector<float> data(num_points * dim);   
    for (auto& val : data) {
        val = distribution(engine);   
    }
    std::vector<float> query(num_queries * dim);
    for (auto& val : query) {
        val = distribution(engine);
    }
    // 转换为FP16类型
    vector<float16_t> data_fp16(num_points * dim);
    for (int i = 0; i < num_points * dim; i++) {
        data_fp16[i] = static_cast<float16_t>(data[i]);
    }
    vector<float16_t> query_fp16(num_queries * dim);
    for (int i = 0; i < num_queries * dim; i++) {
        query_fp16[i] = static_cast<float16_t>(query[i]);
    }

    // 创建距离计算空间和HNSW图
    auto space = std::make_unique<L2SpaceHalf>(dim);
    auto index = std::make_unique<HierarchicalNSWHalf>(space.get(), num_points, M, efc, 100, false);

    // 构建索引
    for (int i = 0; i < num_points; i++) {
        index->addPoint(&data_fp16[i * dim], i);
    }
    if (num_points > 0) {
        char* hnsw_raw = index->data_level0_memory_;
        labeltype* hnsw_label_ptr = reinterpret_cast<labeltype*>(hnsw_raw + index->label_offset_);
    }

    // 搜索
    index->setEf(efs);
    vector<vector<pair<float, labeltype>>> results;
    for (int i = 0; i < num_queries; i++) {
        auto result = index->searchKnn(&query[i * dim], k);
        vector<pair<float, labeltype>> result_vec;
        while (!result.empty()) {
            result_vec.push_back(result.top());
            result.pop();
        }
        std::reverse(result_vec.begin(), result_vec.end());
        results.push_back(result_vec);
    }

    // 保存索引
    string index_file = "test_index_" + distance_type + "_" + data_type + ".bin";
    try{
        index->saveIndex(index_file);
    } catch (const std::exception& e) {
        std::cerr << "Failed to save index: " << e.what() << std::endl;
        index = nullptr;
    }

    // 加载索引
    auto loaded_index = std::make_unique<HierarchicalNSWHalf>(space.get(), 0, M, efc, 100, false);
    loaded_index->loadIndex(index_file, space);
    loaded_index->setEf(efs);
}

优化后的hnswlib性能测试示例

该示例以fashion-mnist-784-euclidean.hdf5数据集为例。

  1. 创建“data”文件夹,下载数据集到“data”文件夹中。
    1
    2
    3
    cd /path/to/hnswlib
    mkdir data && cd data
    wget http://ann-benchmarks.com/fashion-mnist-784-euclidean.hdf5 --no-check-certificate
    
  2. 为脚本添加运行权限。
    cd /path/to/hnswlib
    chmod +x test.sh
  3. 运行测试脚本。
    • FP32数据类型,使用NEON指令。
      ./test.sh hnswlib hnswlib_neon 
    • FP32数据类型,使用SVE指令。
      ./test.sh hnswlib hnswlib_sve
    • FP16数据类型,使用NEON指令。
      ./test.sh hnswlib_fp16 hnswlib_fp16_neon
    • FP16数据类型,使用SVE指令。
      ./test.sh hnswlib_fp16 hnswlib_fp16_sve

    测试结果保存在“output”文件夹中,关注测试结果的qps指标与召回率。生成的测试结果如图所示。

    • “/path/to/hnswlib/configs”文件夹下包含了不同数据集的配置信息,其中,index_path是索引保存路径,save_or_load是图索引构建或加载选项。
    • 当save_or_load选项为save时表示构图模式,此时会将构建的图索引保存到“/path/to/hnswlib/index_path”中。
    • 当save_or_load选项为load时表示加载模式,此时会从“/path/to/hnswlib/index_path”中加载图索引,直接用于后续的搜索。
    • 首次运行时,在save模式和单NUMA的情况下运行性能测试脚本文件,保存图索引。
    • 在后续运行中,如果不需要修改k_f、efs、efc的值,则可以在load模式和整机4NUMA下运行脚本获取性能数据。
    • 如果需要修改k_f、efs、efc的值,则需要在save模式下重新构图。
    • 召回率存在一定随机性,略低于0.99不影响结论。

性能测试参数调优指导

假设参数调优的配置文件路径为“/path/to/hnswlib/configs”,该文件夹的目录结构如下:

├── configs
      ├── hnswlib
      ├── hnswlib_fp16
            └── hnswlib_fp16_fashion-mnist-784-euclidean.config
config文件的内容如下所示:
# HNSWLIB
k_f = 16
efs = 30
efc = 200
metric = L2
nloop = 3
num_threads = 64
top_k = 10
batch_mode = false
batch_size = 100
save_or_load = save
index_path = indexes/hnsw/fashion.faiss

调优的参数如表1所示。

表1 调优参数表

参数

含义

调优建议

k_f

每个节点在HNSW图中的最大连接数。

较小的值可以减少索引的大小,但可能会降低搜索精度;较大的值可以提高搜索精度,但会增加索引的大小和构建时间。

efs

在搜索时,每个节点的候选邻居数量。

较小的值可以加快搜索速度,但可能会降低搜索精度;较大的值可以提高搜索精度,但会增加搜索时间。

efc

在构建索引时,每个节点的候选邻居数量。

较小的值可以加快索引构建速度,但可能会降低索引质量;较大的值可以提高索引的质量,但会增加索引构建时间。

save_or_load = save模式下进行上述参数调优,构建索引时建议使用单NUMA,调优目标为保证召回率大于0.99的情况下,寻找qps最高的参数组合。

优化后的hnswlib功能测试示例

请参见兼容性验证进行功能测试。