Rate This Document
Findability
Accuracy
Completeness
Readability

Examples

This section describes how to use the optimized hnswlib for testing. It provides details on quick start examples, performance test examples, and function test examples, and explains how to tune performance parameters.

Prerequisites

You have compiled hnswlib. For details, see Compiling hnswlib.

Directory Description

Assume that the directory where the program runs is /path/to/hnswlib. The complete directory structure is as follows:
├── configs          // Configuration files related to datasets
├── hnswlib           // hnswlib header files
├── python_bindings    // Python API files
├── include           // Performance test files
├── src              // Performance test header files
├── examples         // Function test files
├── tests             // Function test files
├── test               // Test files, including UT and fuzz test files
└── ALGO_PARAMS.md
└── CMakeLists.txt     // CMakeLists.txt file for function tests
└── LICENSE
└── Makefile           // Makefile file for performance tests
└── MANIFEST.in
└── pyproject.toml
└── README.en.md
└── README.md
└── run.sh             // Script for compiling and executing function tests
└── test.sh           // Script for executing performance tests
└── setup.py
└── TESTING_RECALL.md

Quick Start Examples

The following example demonstrates how to create an index, insert elements, perform search, save the index, and load the index.
#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";

    // Generate FP32 test data.
    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);
    }
    // Convert the data type to 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]);
    }

    // Create the distance computation space and HNSW graph.
    auto space = std::make_unique<L2SpaceHalf>(dim);
    auto index = std::make_unique<HierarchicalNSWHalf>(space.get(), num_points, M, efc, 100, false);

    // Create Index.
    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_);
    }

    // Perform search.
    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);
    }

    // Save the index.
    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;
    }

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

Performance Test Example for the Optimized hnswlib

The following uses the fashion-mnist-784-euclidean.hdf5 dataset as an example.

  1. Create the data folder and download the dataset to the data folder.
    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. Run the following commands to make the script executable:
    cd /path/to/hnswlib
    chmod +x test.sh
  3. Run the test script.
    • For FP32 data type, use the NEON instruction:
      ./test.sh hnswlib hnswlib_neon 
    • For FP32 data type, use the SVE instruction:
      ./test.sh hnswlib hnswlib_sve
    • FP16 data type, use the NEON instruction.
      ./test.sh hnswlib_fp16 hnswlib_fp16_neon
    • For FP16 data type, use the SVE instruction:
      ./test.sh hnswlib_fp16 hnswlib_fp16_sve

    The test results are stored in the output folder. Monitor the QPS metrics and recall rates in the test results. The following figure shows the test result:

    • The /path/to/hnswlib/configs folder contains the configuration information of different datasets. index_path indicates the path for saving the index, and save_or_load specifies whether to construct or load the graph index.
    • If save_or_load is set to save, the graph construction mode is applied. In this case, a constructed graph index is saved to /path/to/hnswlib/index_path.
    • If save_or_load is set to load, the graph index is loaded from /path/to/hnswlib/index_path and directly used for subsequent search.
    • On the first run, execute the performance test script in save mode with single NUMA to save the graph index.
    • In subsequent running, if the values of k_f, efs, and efc do not need to be changed, you can run the script in load mode across all 4 NUMA nodes to obtain performance data.
    • If the values of k_f, efs, and efc need to be changed, you need to reconstruct the graph in save mode.
    • The recall may vary slightly due to randomness; being slightly under 0.99 does not invalidate the findings.

Tuning Guide for Performance Test Parameters

Assume that the path of the configuration file for parameter tuning is /path/to/hnswlib/configs. The directory structure of the folder is as follows:

├── configs
      ├── hnswlib
      ├── hnswlib_fp16
            └── hnswlib_fp16_fashion-mnist-784-euclidean.config
The content of the config file is as follows:
# 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

Table 1 describes the parameters to be tuned.

Table 1 Parameters to be tuned

Parameter

Description

Tuning Suggestion

k_f

Maximum number of connections of each node in the HNSW graph.

A smaller value reduces the index size but may reduce the search precision. A larger value improves the search precision but increases the index size and construction time.

efs

Number of candidate neighbors of each node during search.

A smaller value accelerates the search speed but may reduce the search precision. A larger value improves the search precision but increases the search time.

efc

Number of candidate neighbors of each node during index construction.

A smaller value accelerates the index construction speed but may reduce the index quality. A larger value improves the index quality but increases the index construction time.

Tune the above parameters when save_or_load is set to save. It is recommended that a single NUMA node is used to construct the index. The tuning objective is to identify the parameter combination that achieves the highest QPS while maintaining a recall rate greater than 0.99.

Function Test Example for the Optimized hnswlib

For details about the function test, see Verifying Compatibility.