diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:40:55 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:40:55 +0100 |
| commit | 5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda (patch) | |
| tree | 1acdfa5220cd13b7be43a2a01368e80d306473ca /examples/redis-unstable/modules/vector-sets/hnsw.h | |
| parent | c7ab12bba64d9c20ccd79b132dac475f7bc3923e (diff) | |
| download | crep-5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda.tar.gz | |
Add Redis source code for testing
Diffstat (limited to 'examples/redis-unstable/modules/vector-sets/hnsw.h')
| -rw-r--r-- | examples/redis-unstable/modules/vector-sets/hnsw.h | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/examples/redis-unstable/modules/vector-sets/hnsw.h b/examples/redis-unstable/modules/vector-sets/hnsw.h new file mode 100644 index 0000000..8935521 --- /dev/null +++ b/examples/redis-unstable/modules/vector-sets/hnsw.h | |||
| @@ -0,0 +1,189 @@ | |||
| 1 | /* | ||
| 2 | * HNSW (Hierarchical Navigable Small World) Implementation | ||
| 3 | * Based on the paper by Yu. A. Malkov, D. A. Yashunin | ||
| 4 | * | ||
| 5 | * Copyright (c) 2009-Present, Redis Ltd. | ||
| 6 | * All rights reserved. | ||
| 7 | * | ||
| 8 | * Licensed under your choice of (a) the Redis Source Available License 2.0 | ||
| 9 | * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the | ||
| 10 | * GNU Affero General Public License v3 (AGPLv3). | ||
| 11 | * Originally authored by: Salvatore Sanfilippo. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #ifndef HNSW_H | ||
| 15 | #define HNSW_H | ||
| 16 | |||
| 17 | #include <pthread.h> | ||
| 18 | #include <stdatomic.h> | ||
| 19 | |||
| 20 | #define HNSW_DEFAULT_M 16 /* Used when 0 is given at creation time. */ | ||
| 21 | #define HNSW_MIN_M 4 /* Probably even too low already. */ | ||
| 22 | #define HNSW_MAX_M 4096 /* Safeguard sanity limit. */ | ||
| 23 | #define HNSW_MAX_THREADS 32 /* Maximum number of concurrent threads */ | ||
| 24 | |||
| 25 | /* Quantization types you can enable at creation time in hnsw_new() */ | ||
| 26 | #define HNSW_QUANT_NONE 0 // No quantization. | ||
| 27 | #define HNSW_QUANT_Q8 1 // Q8 quantization. | ||
| 28 | #define HNSW_QUANT_BIN 2 // Binary quantization. | ||
| 29 | |||
| 30 | /* Layer structure for HNSW nodes. Each node will have from one to a few | ||
| 31 | * of this depending on its level. */ | ||
| 32 | typedef struct { | ||
| 33 | struct hnswNode **links; /* Array of neighbors for this layer */ | ||
| 34 | uint32_t num_links; /* Number of used links */ | ||
| 35 | uint32_t max_links; /* Maximum links for this layer. We may | ||
| 36 | * reallocate the node in very particular | ||
| 37 | * conditions in order to allow linking of | ||
| 38 | * new inserted nodes, so this may change | ||
| 39 | * dynamically and be > M*2 for a small set of | ||
| 40 | * nodes. */ | ||
| 41 | float worst_distance; /* Distance to the worst neighbor */ | ||
| 42 | uint32_t worst_idx; /* Index of the worst neighbor */ | ||
| 43 | } hnswNodeLayer; | ||
| 44 | |||
| 45 | /* Node structure for HNSW graph */ | ||
| 46 | typedef struct hnswNode { | ||
| 47 | uint32_t level; /* Node's maximum level */ | ||
| 48 | uint64_t id; /* Unique identifier, may be useful in order to | ||
| 49 | * have a bitmap of visited notes to use as | ||
| 50 | * alternative to epoch / visited_epoch. | ||
| 51 | * Also used in serialization in order to retain | ||
| 52 | * links specifying IDs. */ | ||
| 53 | void *vector; /* The vector, quantized or not. */ | ||
| 54 | float quants_range; /* Quantization range for this vector: | ||
| 55 | * min/max values will be in the range | ||
| 56 | * -quants_range, +quants_range */ | ||
| 57 | float l2; /* L2 before normalization. */ | ||
| 58 | |||
| 59 | /* Last time (epoch) this node was visited. We need one per thread. | ||
| 60 | * This avoids having a different data structure where we track | ||
| 61 | * visited nodes, but costs memory per node. */ | ||
| 62 | uint64_t visited_epoch[HNSW_MAX_THREADS]; | ||
| 63 | |||
| 64 | void *value; /* Associated value */ | ||
| 65 | struct hnswNode *prev, *next; /* Prev/Next node in the list starting at | ||
| 66 | * HNSW->head. */ | ||
| 67 | |||
| 68 | /* Links (and links info) per each layer. Note that this is part | ||
| 69 | * of the node allocation to be more cache friendly: reliable 3% speedup | ||
| 70 | * on Apple silicon, and does not make anything more complex. */ | ||
| 71 | hnswNodeLayer layers[]; | ||
| 72 | } hnswNode; | ||
| 73 | |||
| 74 | struct HNSW; | ||
| 75 | |||
| 76 | /* It is possible to navigate an HNSW with a cursor that guarantees | ||
| 77 | * visiting all the elements that remain in the HNSW from the start to the | ||
| 78 | * end of the process (but not the new ones, so that the process will | ||
| 79 | * eventually finish). Check hnsw_cursor_init(), hnsw_cursor_next() and | ||
| 80 | * hnsw_cursor_free(). */ | ||
| 81 | typedef struct hnswCursor { | ||
| 82 | struct HNSW *index; // Reference to the index of this cursor. | ||
| 83 | hnswNode *current; // Element to report when hnsw_cursor_next() is called. | ||
| 84 | struct hnswCursor *next; // Next cursor active. | ||
| 85 | } hnswCursor; | ||
| 86 | |||
| 87 | /* Main HNSW index structure */ | ||
| 88 | typedef struct HNSW { | ||
| 89 | hnswNode *enter_point; /* Entry point for the graph */ | ||
| 90 | uint32_t M; /* M as in the paper: layer 0 has M*2 max | ||
| 91 | neighbors (M populated at insertion time) | ||
| 92 | while all the other layers have M neighbors. */ | ||
| 93 | uint32_t max_level; /* Current maximum level in the graph */ | ||
| 94 | uint32_t vector_dim; /* Dimensionality of stored vectors */ | ||
| 95 | uint64_t node_count; /* Total number of nodes */ | ||
| 96 | _Atomic uint64_t last_id; /* Last node ID used */ | ||
| 97 | uint64_t current_epoch[HNSW_MAX_THREADS]; /* Current epoch for visit tracking */ | ||
| 98 | hnswNode *head; /* Linked list of nodes. Last first */ | ||
| 99 | |||
| 100 | /* We have two locks here: | ||
| 101 | * 1. A global_lock that is used to perform write operations blocking all | ||
| 102 | * the readers. | ||
| 103 | * 2. One mutex per epoch slot, in order for read operations to acquire | ||
| 104 | * a lock on a specific slot to use epochs tracking of visited nodes. */ | ||
| 105 | pthread_rwlock_t global_lock; /* Global read-write lock */ | ||
| 106 | pthread_mutex_t slot_locks[HNSW_MAX_THREADS]; /* Per-slot locks */ | ||
| 107 | |||
| 108 | _Atomic uint32_t next_slot; /* Next thread slot to try */ | ||
| 109 | _Atomic uint64_t version; /* Version for optimistic concurrency, this is | ||
| 110 | * incremented on deletions and entry point | ||
| 111 | * updates. */ | ||
| 112 | uint32_t quant_type; /* Quantization used. HNSW_QUANT_... */ | ||
| 113 | hnswCursor *cursors; | ||
| 114 | } HNSW; | ||
| 115 | |||
| 116 | /* Serialized node. This structure is used as return value of | ||
| 117 | * hnsw_serialize_node(). */ | ||
| 118 | typedef struct hnswSerNode { | ||
| 119 | void *vector; | ||
| 120 | uint32_t vector_size; | ||
| 121 | uint64_t *params; | ||
| 122 | uint32_t params_count; | ||
| 123 | } hnswSerNode; | ||
| 124 | |||
| 125 | /* Insert preparation context */ | ||
| 126 | typedef struct InsertContext InsertContext; | ||
| 127 | |||
| 128 | /* Core HNSW functions */ | ||
| 129 | HNSW *hnsw_new(uint32_t vector_dim, uint32_t quant_type, uint32_t m); | ||
| 130 | void hnsw_free(HNSW *index,void(*free_value)(void*value)); | ||
| 131 | void hnsw_node_free(hnswNode *node); | ||
| 132 | void hnsw_print_stats(HNSW *index); | ||
| 133 | hnswNode *hnsw_insert(HNSW *index, const float *vector, const int8_t *qvector, | ||
| 134 | float qrange, uint64_t id, void *value, int ef); | ||
| 135 | int hnsw_search(HNSW *index, const float *query, uint32_t k, | ||
| 136 | hnswNode **neighbors, float *distances, uint32_t slot, | ||
| 137 | int query_vector_is_normalized); | ||
| 138 | int hnsw_search_with_filter | ||
| 139 | (HNSW *index, const float *query_vector, uint32_t k, | ||
| 140 | hnswNode **neighbors, float *distances, uint32_t slot, | ||
| 141 | int query_vector_is_normalized, | ||
| 142 | int (*filter_callback)(void *value, void *privdata), | ||
| 143 | void *filter_privdata, uint32_t max_candidates); | ||
| 144 | void hnsw_get_node_vector(HNSW *index, hnswNode *node, float *vec); | ||
| 145 | int hnsw_delete_node(HNSW *index, hnswNode *node, void(*free_value)(void*value)); | ||
| 146 | hnswNode *hnsw_random_node(HNSW *index, int slot); | ||
| 147 | |||
| 148 | /* Thread safety functions. */ | ||
| 149 | int hnsw_acquire_read_slot(HNSW *index); | ||
| 150 | void hnsw_release_read_slot(HNSW *index, int slot); | ||
| 151 | |||
| 152 | /* Optimistic insertion API. */ | ||
| 153 | InsertContext *hnsw_prepare_insert(HNSW *index, const float *vector, const int8_t *qvector, float qrange, uint64_t id, int ef); | ||
| 154 | hnswNode *hnsw_try_commit_insert(HNSW *index, InsertContext *ctx, void *value); | ||
| 155 | void hnsw_free_insert_context(InsertContext *ctx); | ||
| 156 | |||
| 157 | /* Serialization. */ | ||
| 158 | hnswSerNode *hnsw_serialize_node(HNSW *index, hnswNode *node); | ||
| 159 | void hnsw_free_serialized_node(hnswSerNode *sn); | ||
| 160 | hnswNode *hnsw_insert_serialized(HNSW *index, void *vector, uint64_t *params, uint32_t params_len, void *value); | ||
| 161 | int hnsw_deserialize_index(HNSW *index, uint64_t salt0, uint64_t salt1); | ||
| 162 | |||
| 163 | // Helper function in case the user wants to directly copy | ||
| 164 | // the vector bytes. | ||
| 165 | uint32_t hnsw_quants_bytes(HNSW *index); | ||
| 166 | |||
| 167 | /* Cursors. */ | ||
| 168 | hnswCursor *hnsw_cursor_init(HNSW *index); | ||
| 169 | void hnsw_cursor_free(hnswCursor *cursor); | ||
| 170 | hnswNode *hnsw_cursor_next(hnswCursor *cursor); | ||
| 171 | int hnsw_cursor_acquire_lock(hnswCursor *cursor); | ||
| 172 | void hnsw_cursor_release_lock(hnswCursor *cursor); | ||
| 173 | |||
| 174 | /* Allocator selection. */ | ||
| 175 | void hnsw_set_allocator(void (*free_ptr)(void*), void *(*malloc_ptr)(size_t), | ||
| 176 | void *(*realloc_ptr)(void*, size_t)); | ||
| 177 | |||
| 178 | /* Testing. */ | ||
| 179 | int hnsw_validate_graph(HNSW *index, uint64_t *connected_nodes, int *reciprocal_links); | ||
| 180 | void hnsw_test_graph_recall(HNSW *index, int test_ef, int verbose); | ||
| 181 | float hnsw_distance(HNSW *index, hnswNode *a, hnswNode *b); | ||
| 182 | int hnsw_ground_truth_with_filter | ||
| 183 | (HNSW *index, const float *query_vector, uint32_t k, | ||
| 184 | hnswNode **neighbors, float *distances, uint32_t slot, | ||
| 185 | int query_vector_is_normalized, | ||
| 186 | int (*filter_callback)(void *value, void *privdata), | ||
| 187 | void *filter_privdata); | ||
| 188 | |||
| 189 | #endif /* HNSW_H */ | ||
