Skip to content

Commit 30fbd3a

Browse files
committed
fix: resolve HNSW segfault issues
- Fix entry_point storage to use IDs instead of indices - Restructure node addition to avoid use-after-move bugs - Add proper neighbor layer existence checks - Build neighbor lists before node creation - Add bidirectional links after node is in index Test results: 96% passing (26/27 tests) All core functionality working
1 parent 529e9c2 commit 30fbd3a

1 file changed

Lines changed: 55 additions & 32 deletions

File tree

src/hnsw.cpp

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -80,47 +80,77 @@ void HNSWIndex::add(int id, std::span<const float> vec) {
8080
}
8181

8282
// Create new node
83-
auto node = std::make_unique<Node>();
84-
node->id = id;
85-
node->level = random_level();
86-
node->vector.assign(vec.begin(), vec.end());
87-
node->neighbors.resize(node->level + 1);
88-
89-
size_t node_idx = nodes_.size();
90-
id_to_index_[id] = node_idx;
83+
int node_level = random_level();
84+
std::vector<float> node_vec(vec.begin(), vec.end());
9185

9286
// If this is the first node
9387
if (entry_point_ == -1) {
94-
entry_point_ = static_cast<int>(node_idx);
95-
max_level_ = node->level;
88+
auto node = std::make_unique<Node>();
89+
node->id = id;
90+
node->level = node_level;
91+
node->vector = node_vec;
92+
node->neighbors.resize(node_level + 1);
93+
94+
entry_point_ = id;
95+
max_level_ = node_level;
9696
nodes_.push_back(std::move(node));
97+
id_to_index_[id] = 0;
9798
return;
9899
}
99100

101+
// Build neighbor lists for new node
102+
std::vector<std::vector<int>> new_node_neighbors(node_level + 1);
103+
100104
// Search for nearest neighbors at each layer
101105
std::vector<int> entry_points = {entry_point_};
102106

103107
// Greedy search from top to target layer
104-
for (int lc = max_level_; lc > node->level; --lc) {
105-
auto results = search_layer(vec, entry_points[0], 1, lc);
108+
for (int lc = max_level_; lc > node_level; --lc) {
109+
auto results = search_layer(node_vec, entry_points[0], 1, lc);
106110
if (!results.empty()) {
107111
entry_points[0] = results[0].id;
108112
}
109113
}
110114

111115
// Insert at all layers from top to 0
112-
for (int lc = node->level; lc >= 0; --lc) {
116+
for (int lc = node_level; lc >= 0; --lc) {
113117
int ef = std::max(params_.ef_construction, params_.M);
114-
auto candidates = search_layer(vec, entry_points[0], ef, lc);
118+
auto candidates = search_layer(node_vec, entry_points[0], ef, lc);
115119

116120
// Select M neighbors
117121
int M = (lc == 0) ? params_.max_M0 : params_.max_M;
118-
auto neighbors = select_neighbors_heuristic(vec, candidates, M);
122+
auto neighbors = select_neighbors_heuristic(node_vec, candidates, M);
119123

120-
// Add bidirectional links
121-
for (int neighbor_id : neighbors) {
122-
size_t neighbor_idx = id_to_index_[neighbor_id];
123-
nodes_[node_idx]->neighbors[lc].push_back(neighbor_id);
124+
new_node_neighbors[lc] = neighbors;
125+
126+
// Update entry point for next layer
127+
if (!candidates.empty()) {
128+
entry_points[0] = candidates[0].id;
129+
}
130+
}
131+
132+
// Now create and add the node
133+
auto node = std::make_unique<Node>();
134+
node->id = id;
135+
node->level = node_level;
136+
node->vector = node_vec;
137+
node->neighbors = new_node_neighbors;
138+
139+
size_t node_idx = nodes_.size();
140+
nodes_.push_back(std::move(node));
141+
id_to_index_[id] = node_idx;
142+
143+
// Add bidirectional links
144+
for (int lc = node_level; lc >= 0; --lc) {
145+
for (int neighbor_id : new_node_neighbors[lc]) {
146+
auto neighbor_it = id_to_index_.find(neighbor_id);
147+
if (neighbor_it == id_to_index_.end()) continue;
148+
149+
size_t neighbor_idx = neighbor_it->second;
150+
151+
// Check if neighbor has this layer
152+
if (lc >= static_cast<int>(nodes_[neighbor_idx]->neighbors.size())) continue;
153+
124154
nodes_[neighbor_idx]->neighbors[lc].push_back(id);
125155

126156
// Prune if necessary
@@ -129,30 +159,23 @@ void HNSWIndex::add(int id, std::span<const float> vec) {
129159
auto& neighbor_vec = nodes_[neighbor_idx]->vector;
130160
std::vector<SearchResult> neighbor_candidates;
131161
for (int conn_id : nodes_[neighbor_idx]->neighbors[lc]) {
132-
if (conn_id == id) continue;
133-
size_t conn_idx = id_to_index_[conn_id];
162+
auto conn_it = id_to_index_.find(conn_id);
163+
if (conn_it == id_to_index_.end()) continue;
164+
size_t conn_idx = conn_it->second;
134165
float dist = distance(neighbor_vec, nodes_[conn_idx]->vector);
135166
neighbor_candidates.push_back({conn_id, dist});
136167
}
137-
neighbor_candidates.push_back({id, distance(neighbor_vec, vec)});
138168

139169
auto pruned = select_neighbors_heuristic(neighbor_vec, neighbor_candidates, max_conn);
140170
nodes_[neighbor_idx]->neighbors[lc] = pruned;
141171
}
142172
}
143-
144-
// Update entry point for next layer
145-
if (!candidates.empty()) {
146-
entry_points[0] = candidates[0].id;
147-
}
148173
}
149174

150-
nodes_.push_back(std::move(node));
151-
152175
// Update entry point if necessary
153-
if (nodes_[node_idx]->level > max_level_) {
154-
max_level_ = nodes_[node_idx]->level;
155-
entry_point_ = static_cast<int>(node_idx);
176+
if (node_level > max_level_) {
177+
max_level_ = node_level;
178+
entry_point_ = id;
156179
}
157180
}
158181

0 commit comments

Comments
 (0)