aboutsummaryrefslogtreecommitdiff
path: root/examples/redis-unstable/modules/vector-sets/hnsw.h
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:40:55 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:40:55 +0100
commit5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda (patch)
tree1acdfa5220cd13b7be43a2a01368e80d306473ca /examples/redis-unstable/modules/vector-sets/hnsw.h
parentc7ab12bba64d9c20ccd79b132dac475f7bc3923e (diff)
downloadcrep-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.h189
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. */
32typedef 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 */
46typedef 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
74struct 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(). */
81typedef 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 */
88typedef 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(). */
118typedef 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 */
126typedef struct InsertContext InsertContext;
127
128/* Core HNSW functions */
129HNSW *hnsw_new(uint32_t vector_dim, uint32_t quant_type, uint32_t m);
130void hnsw_free(HNSW *index,void(*free_value)(void*value));
131void hnsw_node_free(hnswNode *node);
132void hnsw_print_stats(HNSW *index);
133hnswNode *hnsw_insert(HNSW *index, const float *vector, const int8_t *qvector,
134 float qrange, uint64_t id, void *value, int ef);
135int hnsw_search(HNSW *index, const float *query, uint32_t k,
136 hnswNode **neighbors, float *distances, uint32_t slot,
137 int query_vector_is_normalized);
138int 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);
144void hnsw_get_node_vector(HNSW *index, hnswNode *node, float *vec);
145int hnsw_delete_node(HNSW *index, hnswNode *node, void(*free_value)(void*value));
146hnswNode *hnsw_random_node(HNSW *index, int slot);
147
148/* Thread safety functions. */
149int hnsw_acquire_read_slot(HNSW *index);
150void hnsw_release_read_slot(HNSW *index, int slot);
151
152/* Optimistic insertion API. */
153InsertContext *hnsw_prepare_insert(HNSW *index, const float *vector, const int8_t *qvector, float qrange, uint64_t id, int ef);
154hnswNode *hnsw_try_commit_insert(HNSW *index, InsertContext *ctx, void *value);
155void hnsw_free_insert_context(InsertContext *ctx);
156
157/* Serialization. */
158hnswSerNode *hnsw_serialize_node(HNSW *index, hnswNode *node);
159void hnsw_free_serialized_node(hnswSerNode *sn);
160hnswNode *hnsw_insert_serialized(HNSW *index, void *vector, uint64_t *params, uint32_t params_len, void *value);
161int 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.
165uint32_t hnsw_quants_bytes(HNSW *index);
166
167/* Cursors. */
168hnswCursor *hnsw_cursor_init(HNSW *index);
169void hnsw_cursor_free(hnswCursor *cursor);
170hnswNode *hnsw_cursor_next(hnswCursor *cursor);
171int hnsw_cursor_acquire_lock(hnswCursor *cursor);
172void hnsw_cursor_release_lock(hnswCursor *cursor);
173
174/* Allocator selection. */
175void hnsw_set_allocator(void (*free_ptr)(void*), void *(*malloc_ptr)(size_t),
176 void *(*realloc_ptr)(void*, size_t));
177
178/* Testing. */
179int hnsw_validate_graph(HNSW *index, uint64_t *connected_nodes, int *reciprocal_links);
180void hnsw_test_graph_recall(HNSW *index, int test_ef, int verbose);
181float hnsw_distance(HNSW *index, hnswNode *a, hnswNode *b);
182int 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 */