Faiss
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends
/tmp/faiss/IndexHNSW.cpp
1 /**
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD+Patents license found in the
6  * LICENSE file in the root directory of this source tree.
7  */
8 
9 // -*- c++ -*-
10 
11 #include "IndexHNSW.h"
12 
13 
14 #include <cstdlib>
15 #include <cassert>
16 #include <cstring>
17 #include <cstdio>
18 #include <cmath>
19 #include <omp.h>
20 
21 #include <unordered_set>
22 #include <queue>
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <stdint.h>
28 
29 #ifdef __SSE__
30 #include <immintrin.h>
31 #endif
32 
33 #include "utils.h"
34 #include "Heap.h"
35 #include "FaissAssert.h"
36 #include "IndexFlat.h"
37 #include "IndexIVFPQ.h"
38 
39 
40 extern "C" {
41 
42 /* declare BLAS functions, see http://www.netlib.org/clapack/cblas/ */
43 
44 int sgemm_ (const char *transa, const char *transb, FINTEGER *m, FINTEGER *
45  n, FINTEGER *k, const float *alpha, const float *a,
46  FINTEGER *lda, const float *b, FINTEGER *
47  ldb, float *beta, float *c, FINTEGER *ldc);
48 
49 }
50 
51 namespace faiss {
52 
53 using idx_t = Index::idx_t;
54 using MinimaxHeap = HNSW::MinimaxHeap;
55 using storage_idx_t = HNSW::storage_idx_t;
56 using NodeDistCloser = HNSW::NodeDistCloser;
57 using NodeDistFarther = HNSW::NodeDistFarther;
58 using DistanceComputer = HNSW::DistanceComputer;
59 
60 HNSWStats hnsw_stats;
61 
62 /**************************************************************
63  * add / search blocks of descriptors
64  **************************************************************/
65 
66 namespace {
67 
68 
69 void hnsw_add_vertices(IndexHNSW &index_hnsw,
70  size_t n0,
71  size_t n, const float *x,
72  bool verbose,
73  bool preset_levels = false) {
74  HNSW & hnsw = index_hnsw.hnsw;
75  size_t ntotal = n0 + n;
76  double t0 = getmillisecs();
77  if (verbose) {
78  printf("hnsw_add_vertices: adding %ld elements on top of %ld "
79  "(preset_levels=%d)\n",
80  n, n0, int(preset_levels));
81  }
82 
83  int max_level = hnsw.prepare_level_tab(n, preset_levels);
84 
85  if (verbose) {
86  printf(" max_level = %d\n", max_level);
87  }
88 
89  std::vector<omp_lock_t> locks(ntotal);
90  for(int i = 0; i < ntotal; i++)
91  omp_init_lock(&locks[i]);
92 
93  // add vectors from highest to lowest level
94  std::vector<int> hist;
95  std::vector<int> order(n);
96 
97  { // make buckets with vectors of the same level
98 
99  // build histogram
100  for (int i = 0; i < n; i++) {
101  storage_idx_t pt_id = i + n0;
102  int pt_level = hnsw.levels[pt_id] - 1;
103  while (pt_level >= hist.size())
104  hist.push_back(0);
105  hist[pt_level] ++;
106  }
107 
108  // accumulate
109  std::vector<int> offsets(hist.size() + 1, 0);
110  for (int i = 0; i < hist.size() - 1; i++) {
111  offsets[i + 1] = offsets[i] + hist[i];
112  }
113 
114  // bucket sort
115  for (int i = 0; i < n; i++) {
116  storage_idx_t pt_id = i + n0;
117  int pt_level = hnsw.levels[pt_id] - 1;
118  order[offsets[pt_level]++] = pt_id;
119  }
120  }
121 
122  { // perform add
123  RandomGenerator rng2(789);
124 
125  int i1 = n;
126 
127  for (int pt_level = hist.size() - 1; pt_level >= 0; pt_level--) {
128  int i0 = i1 - hist[pt_level];
129 
130  if (verbose) {
131  printf("Adding %d elements at level %d\n",
132  i1 - i0, pt_level);
133  }
134 
135  // random permutation to get rid of dataset order bias
136  for (int j = i0; j < i1; j++)
137  std::swap(order[j], order[j + rng2.rand_int(i1 - j)]);
138 
139 #pragma omp parallel
140  {
141  VisitedTable vt (ntotal);
142 
143  DistanceComputer *dis = index_hnsw.get_distance_computer();
144  ScopeDeleter1<DistanceComputer> del(dis);
145  int prev_display = verbose && omp_get_thread_num() == 0 ? 0 : -1;
146 
147 #pragma omp for schedule(dynamic)
148  for (int i = i0; i < i1; i++) {
149  storage_idx_t pt_id = order[i];
150  dis->set_query (x + (pt_id - n0) * dis->d);
151 
152  hnsw.add_with_locks(*dis, pt_level, pt_id, locks, vt);
153 
154  if (prev_display >= 0 && i - i0 > prev_display + 10000) {
155  prev_display = i - i0;
156  printf(" %d / %d\r", i - i0, i1 - i0);
157  fflush(stdout);
158  }
159  }
160  }
161  i1 = i0;
162  }
163  FAISS_ASSERT(i1 == 0);
164  }
165  if (verbose) {
166  printf("Done in %.3f ms\n", getmillisecs() - t0);
167  }
168 
169  for(int i = 0; i < ntotal; i++) {
170  omp_destroy_lock(&locks[i]);
171  }
172 }
173 
174 
175 } // namespace
176 
177 
178 
179 
180 /**************************************************************
181  * IndexHNSW implementation
182  **************************************************************/
183 
184 IndexHNSW::IndexHNSW(int d, int M):
185  Index(d, METRIC_L2),
186  hnsw(M),
187  own_fields(false),
188  storage(nullptr),
189  reconstruct_from_neighbors(nullptr)
190 {}
191 
192 IndexHNSW::IndexHNSW(Index *storage, int M):
193  Index(storage->d, METRIC_L2),
194  hnsw(M),
195  own_fields(false),
196  storage(storage),
197  reconstruct_from_neighbors(nullptr)
198 {}
199 
200 IndexHNSW::~IndexHNSW() {
201  if (own_fields) {
202  delete storage;
203  }
204 }
205 
206 void IndexHNSW::train(idx_t n, const float* x)
207 {
208  // hnsw structure does not require training
209  storage->train (n, x);
210  is_trained = true;
211 }
212 
213 void IndexHNSW::search (idx_t n, const float *x, idx_t k,
214  float *distances, idx_t *labels) const
215 
216 {
217 
218 #pragma omp parallel
219  {
220  VisitedTable vt (ntotal);
221  DistanceComputer *dis = get_distance_computer();
223  size_t nreorder = 0;
224 
225 #pragma omp for
226  for(idx_t i = 0; i < n; i++) {
227  idx_t * idxi = labels + i * k;
228  float * simi = distances + i * k;
229  dis->set_query(x + i * d);
230 
231  maxheap_heapify (k, simi, idxi);
232  hnsw.search(*dis, k, idxi, simi, vt);
233 
234  maxheap_reorder (k, simi, idxi);
235 
236  if (reconstruct_from_neighbors &&
237  reconstruct_from_neighbors->k_reorder != 0) {
238  int k_reorder = reconstruct_from_neighbors->k_reorder;
239  if (k_reorder == -1 || k_reorder > k) k_reorder = k;
240 
241  nreorder += reconstruct_from_neighbors->compute_distances(
242  k_reorder, idxi, x + i * d, simi);
243 
244  // sort top k_reorder
245  maxheap_heapify (k_reorder, simi, idxi, simi, idxi, k_reorder);
246  maxheap_reorder (k_reorder, simi, idxi);
247  }
248  }
249 #pragma omp critical
250  {
251  hnsw_stats.nreorder += nreorder;
252  }
253  }
254 
255 
256 }
257 
258 
259 void IndexHNSW::add(idx_t n, const float *x)
260 {
261  FAISS_THROW_IF_NOT(is_trained);
262  int n0 = ntotal;
263  storage->add(n, x);
264  ntotal = storage->ntotal;
265 
266  hnsw_add_vertices (*this, n0, n, x, verbose,
267  hnsw.levels.size() == ntotal);
268 }
269 
271 {
272  hnsw.reset();
273  storage->reset();
274  ntotal = 0;
275 }
276 
277 void IndexHNSW::reconstruct (idx_t key, float* recons) const
278 {
279  storage->reconstruct(key, recons);
280 }
281 
282 void IndexHNSW::shrink_level_0_neighbors(int new_size)
283 {
284 #pragma omp parallel
285  {
286  DistanceComputer *dis = get_distance_computer();
288 
289 #pragma omp for
290  for (idx_t i = 0; i < ntotal; i++) {
291 
292  size_t begin, end;
293  hnsw.neighbor_range(i, 0, &begin, &end);
294 
295  std::priority_queue<NodeDistFarther> initial_list;
296 
297  for (size_t j = begin; j < end; j++) {
298  int v1 = hnsw.neighbors[j];
299  if (v1 < 0) break;
300  initial_list.emplace(dis->symmetric_dis(i, v1), v1);
301 
302  // initial_list.emplace(qdis(v1), v1);
303  }
304 
305  std::vector<NodeDistFarther> shrunk_list;
306  HNSW::shrink_neighbor_list(*dis, initial_list,
307  shrunk_list, new_size);
308 
309  for (size_t j = begin; j < end; j++) {
310  if (j - begin < shrunk_list.size())
311  hnsw.neighbors[j] = shrunk_list[j - begin].id;
312  else
313  hnsw.neighbors[j] = -1;
314  }
315  }
316  }
317 
318 }
319 
321  idx_t n, const float *x, idx_t k,
322  const storage_idx_t *nearest, const float *nearest_d,
323  float *distances, idx_t *labels, int nprobe,
324  int search_type) const
325 {
326 
327  storage_idx_t ntotal = hnsw.levels.size();
328 #pragma omp parallel
329  {
330  DistanceComputer *qdis = get_distance_computer();
332 
333  VisitedTable vt (ntotal);
334 
335 #pragma omp for
336  for(idx_t i = 0; i < n; i++) {
337  idx_t * idxi = labels + i * k;
338  float * simi = distances + i * k;
339 
340  qdis->set_query(x + i * d);
341  maxheap_heapify (k, simi, idxi);
342 
343  if (search_type == 1) {
344 
345  int nres = 0;
346 
347  for(int j = 0; j < nprobe; j++) {
348  storage_idx_t cj = nearest[i * nprobe + j];
349 
350  if (cj < 0) break;
351 
352  if (vt.get(cj)) continue;
353 
354  int candidates_size = std::max(hnsw.efSearch, int(k));
355  MinimaxHeap candidates(candidates_size);
356 
357  candidates.push(cj, nearest_d[i * nprobe + j]);
358 
359  nres = hnsw.search_from_candidates(
360  *qdis, k, idxi, simi,
361  candidates, vt, 0, nres
362  );
363  }
364  } else if (search_type == 2) {
365 
366  int candidates_size = std::max(hnsw.efSearch, int(k));
367  candidates_size = std::max(candidates_size, nprobe);
368 
369  MinimaxHeap candidates(candidates_size);
370  for(int j = 0; j < nprobe; j++) {
371  storage_idx_t cj = nearest[i * nprobe + j];
372 
373  if (cj < 0) break;
374  candidates.push(cj, nearest_d[i * nprobe + j]);
375  }
376  hnsw.search_from_candidates(
377  *qdis, k, idxi, simi,
378  candidates, vt, 0
379  );
380 
381  }
382  vt.advance();
383 
384  maxheap_reorder (k, simi, idxi);
385 
386  }
387  }
388 
389 
390 }
391 
393  int k, const float *D, const idx_t *I)
394 {
395  int dest_size = hnsw.nb_neighbors (0);
396 
397 #pragma omp parallel for
398  for (idx_t i = 0; i < ntotal; i++) {
399  DistanceComputer *qdis = get_distance_computer();
400  float vec[d];
401  storage->reconstruct(i, vec);
402  qdis->set_query(vec);
403 
404  std::priority_queue<NodeDistFarther> initial_list;
405 
406  for (size_t j = 0; j < k; j++) {
407  int v1 = I[i * k + j];
408  if (v1 == i) continue;
409  if (v1 < 0) break;
410  initial_list.emplace(D[i * k + j], v1);
411  }
412 
413  std::vector<NodeDistFarther> shrunk_list;
414  HNSW::shrink_neighbor_list(*qdis, initial_list, shrunk_list, dest_size);
415 
416  size_t begin, end;
417  hnsw.neighbor_range(i, 0, &begin, &end);
418 
419  for (size_t j = begin; j < end; j++) {
420  if (j - begin < shrunk_list.size())
421  hnsw.neighbors[j] = shrunk_list[j - begin].id;
422  else
423  hnsw.neighbors[j] = -1;
424  }
425  }
426 }
427 
428 
429 
431  int n, const storage_idx_t *points,
432  const storage_idx_t *nearests)
433 {
434 
435  std::vector<omp_lock_t> locks(ntotal);
436  for(int i = 0; i < ntotal; i++)
437  omp_init_lock(&locks[i]);
438 
439 #pragma omp parallel
440  {
441  VisitedTable vt (ntotal);
442 
443  DistanceComputer *dis = get_distance_computer();
445  float vec[storage->d];
446 
447 #pragma omp for schedule(dynamic)
448  for (int i = 0; i < n; i++) {
449  storage_idx_t pt_id = points[i];
450  storage_idx_t nearest = nearests[i];
451  storage->reconstruct (pt_id, vec);
452  dis->set_query (vec);
453 
454  hnsw.add_links_starting_from(*dis, pt_id,
455  nearest, (*dis)(nearest),
456  0, locks.data(), vt);
457 
458  if (verbose && i % 10000 == 0) {
459  printf(" %d / %d\r", i, n);
460  fflush(stdout);
461  }
462  }
463  }
464  if (verbose) {
465  printf("\n");
466  }
467 
468  for(int i = 0; i < ntotal; i++)
469  omp_destroy_lock(&locks[i]);
470 }
471 
472 void IndexHNSW::reorder_links()
473 {
474  int M = hnsw.nb_neighbors(0);
475 
476 #pragma omp parallel
477  {
478  std::vector<float> distances (M);
479  std::vector<size_t> order (M);
480  std::vector<storage_idx_t> tmp (M);
481  DistanceComputer *dis = get_distance_computer();
483 
484 #pragma omp for
485  for(storage_idx_t i = 0; i < ntotal; i++) {
486 
487  size_t begin, end;
488  hnsw.neighbor_range(i, 0, &begin, &end);
489 
490  for (size_t j = begin; j < end; j++) {
491  storage_idx_t nj = hnsw.neighbors[j];
492  if (nj < 0) {
493  end = j;
494  break;
495  }
496  distances[j - begin] = dis->symmetric_dis(i, nj);
497  tmp [j - begin] = nj;
498  }
499 
500  fvec_argsort (end - begin, distances.data(), order.data());
501  for (size_t j = begin; j < end; j++) {
502  hnsw.neighbors[j] = tmp[order[j - begin]];
503  }
504  }
505 
506  }
507 }
508 
509 
510 void IndexHNSW::link_singletons()
511 {
512  printf("search for singletons\n");
513 
514  std::vector<bool> seen(ntotal);
515 
516  for (size_t i = 0; i < ntotal; i++) {
517  size_t begin, end;
518  hnsw.neighbor_range(i, 0, &begin, &end);
519  for (size_t j = begin; j < end; j++) {
520  storage_idx_t ni = hnsw.neighbors[j];
521  if (ni >= 0) seen[ni] = true;
522  }
523  }
524 
525  int n_sing = 0, n_sing_l1 = 0;
526  std::vector<storage_idx_t> singletons;
527  for (storage_idx_t i = 0; i < ntotal; i++) {
528  if (!seen[i]) {
529  singletons.push_back(i);
530  n_sing++;
531  if (hnsw.levels[i] > 1)
532  n_sing_l1++;
533  }
534  }
535 
536  printf(" Found %d / %ld singletons (%d appear in a level above)\n",
537  n_sing, ntotal, n_sing_l1);
538 
539  std::vector<float>recons(singletons.size() * d);
540  for (int i = 0; i < singletons.size(); i++) {
541 
542  FAISS_ASSERT(!"not implemented");
543 
544  }
545 
546 
547 }
548 
549 
550 namespace {
551 
552 
553 // storage that explicitly reconstructs vectors before computing distances
554 struct GenericDistanceComputer: DistanceComputer {
555 
556  const Index & storage;
557  std::vector<float> buf;
558  const float *q;
559 
560  GenericDistanceComputer(const Index & storage): storage(storage)
561  {
562  d = storage.d;
563  buf.resize(d * 2);
564  }
565 
566  float operator () (storage_idx_t i) override
567  {
568  storage.reconstruct(i, buf.data());
569  return fvec_L2sqr(q, buf.data(), d);
570  }
571 
572  float symmetric_dis(storage_idx_t i, storage_idx_t j) override
573  {
574  storage.reconstruct(i, buf.data());
575  storage.reconstruct(j, buf.data() + d);
576  return fvec_L2sqr(buf.data() + d, buf.data(), d);
577  }
578 
579  void set_query(const float *x) override {
580  q = x;
581  }
582 
583 
584 };
585 
586 
587 } // namespace
588 
589 DistanceComputer * IndexHNSW::get_distance_computer () const
590 {
591  return new GenericDistanceComputer (*storage);
592 }
593 
594 
595 /**************************************************************
596  * ReconstructFromNeighbors implementation
597  **************************************************************/
598 
599 
600 ReconstructFromNeighbors::ReconstructFromNeighbors(
601  const IndexHNSW & index, size_t k, size_t nsq):
602  index(index), k(k), nsq(nsq) {
603  M = index.hnsw.nb_neighbors(0);
604  FAISS_ASSERT(k <= 256);
605  code_size = k == 1 ? 0 : nsq;
606  ntotal = 0;
607  d = index.d;
608  FAISS_ASSERT(d % nsq == 0);
609  dsub = d / nsq;
610  k_reorder = -1;
611 }
612 
613 void ReconstructFromNeighbors::reconstruct(storage_idx_t i, float *x, float *tmp) const
614 {
615 
616 
617  const HNSW & hnsw = index.hnsw;
618  size_t begin, end;
619  hnsw.neighbor_range(i, 0, &begin, &end);
620 
621  if (k == 1 || nsq == 1) {
622  const float * beta;
623  if (k == 1) {
624  beta = codebook.data();
625  } else {
626  int idx = codes[i];
627  beta = codebook.data() + idx * (M + 1);
628  }
629 
630  float w0 = beta[0]; // weight of image itself
631  index.storage->reconstruct(i, tmp);
632 
633  for (int l = 0; l < d; l++)
634  x[l] = w0 * tmp[l];
635 
636  for (size_t j = begin; j < end; j++) {
637 
638  storage_idx_t ji = hnsw.neighbors[j];
639  if (ji < 0) ji = i;
640  float w = beta[j - begin + 1];
641  index.storage->reconstruct(ji, tmp);
642  for (int l = 0; l < d; l++)
643  x[l] += w * tmp[l];
644  }
645  } else if (nsq == 2) {
646  int idx0 = codes[2 * i];
647  int idx1 = codes[2 * i + 1];
648 
649  const float *beta0 = codebook.data() + idx0 * (M + 1);
650  const float *beta1 = codebook.data() + (idx1 + k) * (M + 1);
651 
652  index.storage->reconstruct(i, tmp);
653 
654  float w0;
655 
656  w0 = beta0[0];
657  for (int l = 0; l < dsub; l++)
658  x[l] = w0 * tmp[l];
659 
660  w0 = beta1[0];
661  for (int l = dsub; l < d; l++)
662  x[l] = w0 * tmp[l];
663 
664  for (size_t j = begin; j < end; j++) {
665  storage_idx_t ji = hnsw.neighbors[j];
666  if (ji < 0) ji = i;
667  index.storage->reconstruct(ji, tmp);
668  float w;
669  w = beta0[j - begin + 1];
670  for (int l = 0; l < dsub; l++)
671  x[l] += w * tmp[l];
672 
673  w = beta1[j - begin + 1];
674  for (int l = dsub; l < d; l++)
675  x[l] += w * tmp[l];
676  }
677  } else {
678  const float *betas[nsq];
679  {
680  const float *b = codebook.data();
681  const uint8_t *c = &codes[i * code_size];
682  for (int sq = 0; sq < nsq; sq++) {
683  betas[sq] = b + (*c++) * (M + 1);
684  b += (M + 1) * k;
685  }
686  }
687 
688  index.storage->reconstruct(i, tmp);
689  {
690  int d0 = 0;
691  for (int sq = 0; sq < nsq; sq++) {
692  float w = *(betas[sq]++);
693  int d1 = d0 + dsub;
694  for (int l = d0; l < d1; l++) {
695  x[l] = w * tmp[l];
696  }
697  d0 = d1;
698  }
699  }
700 
701  for (size_t j = begin; j < end; j++) {
702  storage_idx_t ji = hnsw.neighbors[j];
703  if (ji < 0) ji = i;
704 
705  index.storage->reconstruct(ji, tmp);
706  int d0 = 0;
707  for (int sq = 0; sq < nsq; sq++) {
708  float w = *(betas[sq]++);
709  int d1 = d0 + dsub;
710  for (int l = d0; l < d1; l++) {
711  x[l] += w * tmp[l];
712  }
713  d0 = d1;
714  }
715  }
716  }
717 }
718 
719 void ReconstructFromNeighbors::reconstruct_n(storage_idx_t n0,
720  storage_idx_t ni,
721  float *x) const
722 {
723 #pragma omp parallel
724  {
725  std::vector<float> tmp(index.d);
726 #pragma omp for
727  for (storage_idx_t i = 0; i < ni; i++) {
728  reconstruct(n0 + i, x + i * index.d, tmp.data());
729  }
730  }
731 }
732 
733 size_t ReconstructFromNeighbors::compute_distances(
734  size_t n, const idx_t *shortlist,
735  const float *query, float *distances) const
736 {
737  std::vector<float> tmp(2 * index.d);
738  size_t ncomp = 0;
739  for (int i = 0; i < n; i++) {
740  if (shortlist[i] < 0) break;
741  reconstruct(shortlist[i], tmp.data(), tmp.data() + index.d);
742  distances[i] = fvec_L2sqr(query, tmp.data(), index.d);
743  ncomp++;
744  }
745  return ncomp;
746 }
747 
748 void ReconstructFromNeighbors::get_neighbor_table(storage_idx_t i, float *tmp1) const
749 {
750  const HNSW & hnsw = index.hnsw;
751  size_t begin, end;
752  hnsw.neighbor_range(i, 0, &begin, &end);
753  size_t d = index.d;
754 
755  index.storage->reconstruct(i, tmp1);
756 
757  for (size_t j = begin; j < end; j++) {
758  storage_idx_t ji = hnsw.neighbors[j];
759  if (ji < 0) ji = i;
760  index.storage->reconstruct(ji, tmp1 + (j - begin + 1) * d);
761  }
762 
763 }
764 
765 
766 /// called by add_codes
768  const float *x, storage_idx_t i, uint8_t *code) const
769 {
770 
771  // fill in tmp table with the neighbor values
772  float *tmp1 = new float[d * (M + 1) + (d * k)];
773  float *tmp2 = tmp1 + d * (M + 1);
774  ScopeDeleter<float> del(tmp1);
775 
776  // collect coordinates of base
777  get_neighbor_table (i, tmp1);
778 
779  for (size_t sq = 0; sq < nsq; sq++) {
780  int d0 = sq * dsub;
781 
782  {
783  FINTEGER ki = k, di = d, m1 = M + 1;
784  FINTEGER dsubi = dsub;
785  float zero = 0, one = 1;
786 
787  sgemm_ ("N", "N", &dsubi, &ki, &m1, &one,
788  tmp1 + d0, &di,
789  codebook.data() + sq * (m1 * k), &m1,
790  &zero, tmp2, &dsubi);
791  }
792 
793  float min = HUGE_VAL;
794  int argmin = -1;
795  for (size_t j = 0; j < k; j++) {
796  float dis = fvec_L2sqr(x + d0, tmp2 + j * dsub, dsub);
797  if (dis < min) {
798  min = dis;
799  argmin = j;
800  }
801  }
802  code[sq] = argmin;
803  }
804 
805 }
806 
807 void ReconstructFromNeighbors::add_codes(size_t n, const float *x)
808 {
809  if (k == 1) { // nothing to encode
810  ntotal += n;
811  return;
812  }
813  codes.resize(codes.size() + code_size * n);
814 #pragma omp parallel for
815  for (int i = 0; i < n; i++) {
816  estimate_code(x + i * index.d, ntotal + i,
817  codes.data() + (ntotal + i) * code_size);
818  }
819  ntotal += n;
820  FAISS_ASSERT (codes.size() == ntotal * code_size);
821 }
822 
823 
824 /**************************************************************
825  * IndexHNSWFlat implementation
826  **************************************************************/
827 
828 
829 namespace {
830 
831 
832 struct FlatL2Dis: DistanceComputer {
833  Index::idx_t nb;
834  const float *q;
835  const float *b;
836  size_t ndis;
837 
838  float operator () (storage_idx_t i) override
839  {
840  ndis++;
841  return (fvec_L2sqr(q, b + i * d, d));
842  }
843 
844  float symmetric_dis(storage_idx_t i, storage_idx_t j) override
845  {
846  return (fvec_L2sqr(b + j * d, b + i * d, d));
847  }
848 
849 
850  FlatL2Dis(const IndexFlatL2 & storage, const float *q = nullptr):
851  q(q)
852  {
853  nb = storage.ntotal;
854  d = storage.d;
855  b = storage.xb.data();
856  ndis = 0;
857  }
858 
859  void set_query(const float *x) override {
860  q = x;
861  }
862 
863  virtual ~FlatL2Dis () {
864 #pragma omp critical
865  {
866  hnsw_stats.ndis += ndis;
867  }
868  }
869 };
870 
871 
872 } // namespace
873 
874 
875 IndexHNSWFlat::IndexHNSWFlat()
876 {
877  is_trained = true;
878 }
879 
880 
881 IndexHNSWFlat::IndexHNSWFlat(int d, int M):
882  IndexHNSW(new IndexFlatL2(d), M)
883 {
884  own_fields = true;
885  is_trained = true;
886 }
887 
888 
889 DistanceComputer * IndexHNSWFlat::get_distance_computer () const
890 {
891  return new FlatL2Dis (*dynamic_cast<IndexFlatL2*> (storage));
892 }
893 
894 
895 
896 
897 /**************************************************************
898  * IndexHNSWPQ implementation
899  **************************************************************/
900 
901 
902 namespace {
903 
904 
905 struct PQDis: DistanceComputer {
906  Index::idx_t nb;
907  const uint8_t *codes;
908  size_t code_size;
909  const ProductQuantizer & pq;
910  const float *sdc;
911  std::vector<float> precomputed_table;
912  size_t ndis;
913 
914  float operator () (storage_idx_t i) override
915  {
916  const uint8_t *code = codes + i * code_size;
917  const float *dt = precomputed_table.data();
918  float accu = 0;
919  for (int j = 0; j < pq.M; j++) {
920  accu += dt[*code++];
921  dt += 256;
922  }
923  ndis++;
924  return accu;
925  }
926 
927  float symmetric_dis(storage_idx_t i, storage_idx_t j) override
928  {
929  const float * sdci = sdc;
930  float accu = 0;
931  const uint8_t *codei = codes + i * code_size;
932  const uint8_t *codej = codes + j * code_size;
933 
934  for (int l = 0; l < pq.M; l++) {
935  accu += sdci[(*codei++) + (*codej++) * 256];
936  sdci += 256 * 256;
937  }
938  return accu;
939  }
940 
941  PQDis(const IndexPQ& storage, const float* /*q*/ = nullptr)
942  : pq(storage.pq) {
943  precomputed_table.resize(pq.M * pq.ksub);
944  nb = storage.ntotal;
945  d = storage.d;
946  codes = storage.codes.data();
947  code_size = pq.code_size;
948  FAISS_ASSERT(pq.ksub == 256);
949  FAISS_ASSERT(pq.sdc_table.size() == pq.ksub * pq.ksub * pq.M);
950  sdc = pq.sdc_table.data();
951  ndis = 0;
952  }
953 
954  void set_query(const float *x) override {
955  pq.compute_distance_table(x, precomputed_table.data());
956  }
957 
958  virtual ~PQDis () {
959 #pragma omp critical
960  {
961  hnsw_stats.ndis += ndis;
962  }
963  }
964 };
965 
966 
967 } // namespace
968 
969 
970 IndexHNSWPQ::IndexHNSWPQ() {}
971 
972 IndexHNSWPQ::IndexHNSWPQ(int d, int pq_m, int M):
973  IndexHNSW(new IndexPQ(d, pq_m, 8), M)
974 {
975  own_fields = true;
976  is_trained = false;
977 }
978 
979 void IndexHNSWPQ::train(idx_t n, const float* x)
980 {
981  IndexHNSW::train (n, x);
982  (dynamic_cast<IndexPQ*> (storage))->pq.compute_sdc_table();
983 }
984 
985 
986 
987 DistanceComputer * IndexHNSWPQ::get_distance_computer () const
988 {
989  return new PQDis (*dynamic_cast<IndexPQ*> (storage));
990 }
991 
992 
993 /**************************************************************
994  * IndexHNSWSQ implementation
995  **************************************************************/
996 
997 
998 namespace {
999 
1000 
1001 struct SQDis: DistanceComputer {
1002  Index::idx_t nb;
1003  const uint8_t *codes;
1004  size_t code_size;
1005  const ScalarQuantizer & sq;
1006  const float *q;
1007  ScalarQuantizer::DistanceComputer * dc;
1008 
1009  float operator () (storage_idx_t i) override
1010  {
1011  const uint8_t *code = codes + i * code_size;
1012 
1013  return dc->compute_distance (q, code);
1014  }
1015 
1016  float symmetric_dis(storage_idx_t i, storage_idx_t j) override
1017  {
1018  const uint8_t *codei = codes + i * code_size;
1019  const uint8_t *codej = codes + j * code_size;
1020  return dc->compute_code_distance (codei, codej);
1021  }
1022 
1023  SQDis(const IndexScalarQuantizer& storage, const float* /*q*/ = nullptr)
1024  : sq(storage.sq) {
1025  nb = storage.ntotal;
1026  d = storage.d;
1027  codes = storage.codes.data();
1028  code_size = sq.code_size;
1029  dc = sq.get_distance_computer();
1030  }
1031 
1032  void set_query(const float *x) override {
1033  q = x;
1034  }
1035 
1036  virtual ~SQDis () {
1037  delete dc;
1038  }
1039 };
1040 
1041 
1042 } // namespace
1043 
1044 
1045 IndexHNSWSQ::IndexHNSWSQ(int d, ScalarQuantizer::QuantizerType qtype, int M):
1046  IndexHNSW (new IndexScalarQuantizer (d, qtype), M)
1047 {
1048  own_fields = true;
1049 }
1050 
1051 IndexHNSWSQ::IndexHNSWSQ() {}
1052 
1053 DistanceComputer * IndexHNSWSQ::get_distance_computer () const
1054 {
1055  return new SQDis (*dynamic_cast<IndexScalarQuantizer*> (storage));
1056 }
1057 
1058 
1059 
1060 
1061 /**************************************************************
1062  * IndexHNSW2Level implementation
1063  **************************************************************/
1064 
1065 
1066 
1067 IndexHNSW2Level::IndexHNSW2Level(Index *quantizer, size_t nlist, int m_pq, int M):
1068  IndexHNSW (new Index2Layer (quantizer, nlist, m_pq), M)
1069 {
1070  own_fields = true;
1071  is_trained = false;
1072 }
1073 
1074 IndexHNSW2Level::IndexHNSW2Level() {}
1075 
1076 
1077 namespace {
1078 
1079 
1080 struct Distance2Level: DistanceComputer {
1081 
1082  const Index2Layer & storage;
1083  std::vector<float> buf;
1084  const float *q;
1085 
1086  const float *pq_l1_tab, *pq_l2_tab;
1087 
1088  Distance2Level(const Index2Layer & storage): storage(storage)
1089  {
1090  d = storage.d;
1091  FAISS_ASSERT(storage.pq.dsub == 4);
1092  pq_l2_tab = storage.pq.centroids.data();
1093  buf.resize(2 * d);
1094  }
1095 
1096  float symmetric_dis(storage_idx_t i, storage_idx_t j) override
1097  {
1098  storage.reconstruct(i, buf.data());
1099  storage.reconstruct(j, buf.data() + d);
1100  return fvec_L2sqr(buf.data() + d, buf.data(), d);
1101  }
1102 
1103  void set_query(const float *x) override {
1104  q = x;
1105  }
1106 };
1107 
1108 
1109 // well optimized for xNN+PQNN
1110 struct DistanceXPQ4: Distance2Level {
1111 
1112  int M, k;
1113 
1114  DistanceXPQ4(const Index2Layer & storage):
1115  Distance2Level (storage)
1116  {
1117  const IndexFlat *quantizer =
1118  dynamic_cast<IndexFlat*> (storage.q1.quantizer);
1119 
1120  FAISS_ASSERT(quantizer);
1121  M = storage.pq.M;
1122  pq_l1_tab = quantizer->xb.data();
1123  }
1124 
1125  float operator () (storage_idx_t i) override
1126  {
1127 #ifdef __SSE__
1128  const uint8_t *code = storage.codes.data() + i * storage.code_size;
1129  long key = 0;
1130  memcpy (&key, code, storage.code_size_1);
1131  code += storage.code_size_1;
1132 
1133  // walking pointers
1134  const float *qa = q;
1135  const __m128 *l1_t = (const __m128 *)(pq_l1_tab + d * key);
1136  const __m128 *pq_l2_t = (const __m128 *)pq_l2_tab;
1137  __m128 accu = _mm_setzero_ps();
1138 
1139  for (int m = 0; m < M; m++) {
1140  __m128 qi = _mm_loadu_ps(qa);
1141  __m128 recons = l1_t[m] + pq_l2_t[*code++];
1142  __m128 diff = qi - recons;
1143  accu += diff * diff;
1144  pq_l2_t += 256;
1145  qa += 4;
1146  }
1147 
1148  accu = _mm_hadd_ps (accu, accu);
1149  accu = _mm_hadd_ps (accu, accu);
1150  return _mm_cvtss_f32 (accu);
1151 #else
1152  FAISS_THROW_MSG("not implemented for non-x64 platforms");
1153 #endif
1154  }
1155 
1156 };
1157 
1158 // well optimized for 2xNN+PQNN
1159 struct Distance2xXPQ4: Distance2Level {
1160 
1161  int M_2, mi_nbits;
1162 
1163  Distance2xXPQ4(const Index2Layer & storage):
1164  Distance2Level (storage)
1165  {
1166  const MultiIndexQuantizer *mi =
1167  dynamic_cast<MultiIndexQuantizer*> (storage.q1.quantizer);
1168 
1169  FAISS_ASSERT(mi);
1170  FAISS_ASSERT(storage.pq.M % 2 == 0);
1171  M_2 = storage.pq.M / 2;
1172  mi_nbits = mi->pq.nbits;
1173  pq_l1_tab = mi->pq.centroids.data();
1174  }
1175 
1176  float operator () (storage_idx_t i) override
1177  {
1178  const uint8_t *code = storage.codes.data() + i * storage.code_size;
1179  long key01 = 0;
1180  memcpy (&key01, code, storage.code_size_1);
1181  code += storage.code_size_1;
1182 #ifdef __SSE__
1183 
1184  // walking pointers
1185  const float *qa = q;
1186  const __m128 *pq_l1_t = (const __m128 *)pq_l1_tab;
1187  const __m128 *pq_l2_t = (const __m128 *)pq_l2_tab;
1188  __m128 accu = _mm_setzero_ps();
1189 
1190  for (int mi_m = 0; mi_m < 2; mi_m++) {
1191  long l1_idx = key01 & ((1L << mi_nbits) - 1);
1192  const __m128 * pq_l1 = pq_l1_t + M_2 * l1_idx;
1193 
1194  for (int m = 0; m < M_2; m++) {
1195  __m128 qi = _mm_loadu_ps(qa);
1196  __m128 recons = pq_l1[m] + pq_l2_t[*code++];
1197  __m128 diff = qi - recons;
1198  accu += diff * diff;
1199  pq_l2_t += 256;
1200  qa += 4;
1201  }
1202  pq_l1_t += M_2 << mi_nbits;
1203  key01 >>= mi_nbits;
1204  }
1205  accu = _mm_hadd_ps (accu, accu);
1206  accu = _mm_hadd_ps (accu, accu);
1207  return _mm_cvtss_f32 (accu);
1208 #else
1209  FAISS_THROW_MSG("not implemented for non-x64 platforms");
1210 #endif
1211  }
1212 
1213 };
1214 
1215 
1216 } // namespace
1217 
1218 
1219 DistanceComputer * IndexHNSW2Level::get_distance_computer () const
1220 {
1221  const Index2Layer *storage2l =
1222  dynamic_cast<Index2Layer*>(storage);
1223 
1224  if (storage2l) {
1225 #ifdef __SSE__
1226 
1227  const MultiIndexQuantizer *mi =
1228  dynamic_cast<MultiIndexQuantizer*> (storage2l->q1.quantizer);
1229 
1230  if (mi && storage2l->pq.M % 2 == 0 && storage2l->pq.dsub == 4) {
1231  return new Distance2xXPQ4(*storage2l);
1232  }
1233 
1234  const IndexFlat *fl =
1235  dynamic_cast<IndexFlat*> (storage2l->q1.quantizer);
1236 
1237  if (fl && storage2l->pq.dsub == 4) {
1238  return new DistanceXPQ4(*storage2l);
1239  }
1240 #endif
1241  }
1242 
1243  // IVFPQ and cases not handled above
1244  return new GenericDistanceComputer (*storage);
1245 
1246 }
1247 
1248 
1249 namespace {
1250 
1251 
1252 // same as search_from_candidates but uses v
1253 // visno -> is in result list
1254 // visno + 1 -> in result list + in candidates
1255 int search_from_candidates_2(const HNSW & hnsw,
1256  DistanceComputer & qdis, int k,
1257  idx_t *I, float * D,
1258  MinimaxHeap &candidates,
1259  VisitedTable &vt,
1260  int level, int nres_in = 0)
1261 {
1262  int nres = nres_in;
1263  int ndis = 0;
1264  for (int i = 0; i < candidates.size(); i++) {
1265  idx_t v1 = candidates.ids[i];
1266  FAISS_ASSERT(v1 >= 0);
1267  vt.visited[v1] = vt.visno + 1;
1268  }
1269 
1270  int nstep = 0;
1271 
1272  while (candidates.size() > 0) {
1273  float d0 = 0;
1274  int v0 = candidates.pop_min(&d0);
1275 
1276  size_t begin, end;
1277  hnsw.neighbor_range(v0, level, &begin, &end);
1278 
1279  for (size_t j = begin; j < end; j++) {
1280  int v1 = hnsw.neighbors[j];
1281  if (v1 < 0) break;
1282  if (vt.visited[v1] == vt.visno + 1) {
1283  // nothing to do
1284  } else {
1285  ndis++;
1286  float d = qdis(v1);
1287  candidates.push(v1, d);
1288 
1289  // never seen before --> add to heap
1290  if (vt.visited[v1] < vt.visno) {
1291  if (nres < k) {
1292  faiss::maxheap_push (++nres, D, I, d, v1);
1293  } else if (d < D[0]) {
1294  faiss::maxheap_pop (nres--, D, I);
1295  faiss::maxheap_push (++nres, D, I, d, v1);
1296  }
1297  }
1298  vt.visited[v1] = vt.visno + 1;
1299  }
1300  }
1301 
1302  nstep++;
1303  if (nstep > hnsw.efSearch) {
1304  break;
1305  }
1306  }
1307 
1308  if (level == 0) {
1309 #pragma omp critical
1310  {
1311  hnsw_stats.n1 ++;
1312  if (candidates.size() == 0)
1313  hnsw_stats.n2 ++;
1314  }
1315  }
1316 
1317 
1318  return nres;
1319 }
1320 
1321 
1322 } // namespace
1323 
1324 void IndexHNSW2Level::search (idx_t n, const float *x, idx_t k,
1325  float *distances, idx_t *labels) const
1326 {
1327  if (dynamic_cast<const Index2Layer*>(storage)) {
1328  IndexHNSW::search (n, x, k, distances, labels);
1329 
1330  } else { // "mixed" search
1331 
1332  const IndexIVFPQ *index_ivfpq =
1333  dynamic_cast<const IndexIVFPQ*>(storage);
1334 
1335  int nprobe = index_ivfpq->nprobe;
1336 
1337  long * coarse_assign = new long [n * nprobe];
1338  ScopeDeleter<long> del (coarse_assign);
1339  float * coarse_dis = new float [n * nprobe];
1340  ScopeDeleter<float> del2 (coarse_dis);
1341 
1342  index_ivfpq->quantizer->search (n, x, nprobe, coarse_dis, coarse_assign);
1343 
1344  index_ivfpq->search_preassigned (
1345  n, x, k, coarse_assign, coarse_dis, distances, labels, false);
1346 
1347 #pragma omp parallel
1348  {
1349  VisitedTable vt (ntotal);
1350  DistanceComputer *dis = get_distance_computer();
1352 
1353  int candidates_size = hnsw.upper_beam;
1354  MinimaxHeap candidates(candidates_size);
1355 
1356 #pragma omp for
1357  for(idx_t i = 0; i < n; i++) {
1358  idx_t * idxi = labels + i * k;
1359  float * simi = distances + i * k;
1360  dis->set_query(x + i * d);
1361 
1362  // mark all inverted list elements as visited
1363 
1364  for (int j = 0; j < nprobe; j++) {
1365  idx_t key = coarse_assign[j + i * nprobe];
1366  if (key < 0) break;
1367  size_t list_length = index_ivfpq->get_list_size (key);
1368  const idx_t * ids = index_ivfpq->invlists->get_ids (key);
1369 
1370  for (int jj = 0; jj < list_length; jj++) {
1371  vt.set (ids[jj]);
1372  }
1373  }
1374 
1375  candidates.clear();
1376  // copy the upper_beam elements to candidates list
1377 
1378  int search_policy = 2;
1379 
1380  if (search_policy == 1) {
1381 
1382  for (int j = 0 ; j < hnsw.upper_beam && j < k; j++) {
1383  if (idxi[j] < 0) break;
1384  candidates.push (idxi[j], simi[j]);
1385  // search_from_candidates adds them back
1386  idxi[j] = -1;
1387  simi[j] = HUGE_VAL;
1388  }
1389 
1390  // reorder from sorted to heap
1391  maxheap_heapify (k, simi, idxi, simi, idxi, k);
1392 
1393  hnsw.search_from_candidates(
1394  *dis, k, idxi, simi,
1395  candidates, vt, 0, k
1396  );
1397 
1398  vt.advance();
1399 
1400  } else if (search_policy == 2) {
1401 
1402  for (int j = 0 ; j < hnsw.upper_beam && j < k; j++) {
1403  if (idxi[j] < 0) break;
1404  candidates.push (idxi[j], simi[j]);
1405  }
1406 
1407  // reorder from sorted to heap
1408  maxheap_heapify (k, simi, idxi, simi, idxi, k);
1409 
1410  search_from_candidates_2 (
1411  hnsw, *dis, k, idxi, simi,
1412  candidates, vt, 0, k);
1413  vt.advance ();
1414  vt.advance ();
1415 
1416  }
1417 
1418  maxheap_reorder (k, simi, idxi);
1419  }
1420  }
1421  }
1422 
1423 
1424 }
1425 
1426 
1427 void IndexHNSW2Level::flip_to_ivf ()
1428 {
1429  Index2Layer *storage2l =
1430  dynamic_cast<Index2Layer*>(storage);
1431 
1432  FAISS_THROW_IF_NOT (storage2l);
1433 
1434  IndexIVFPQ * index_ivfpq =
1435  new IndexIVFPQ (storage2l->q1.quantizer,
1436  d, storage2l->q1.nlist,
1437  storage2l->pq.M, 8);
1438  index_ivfpq->pq = storage2l->pq;
1439  index_ivfpq->is_trained = storage2l->is_trained;
1440  index_ivfpq->precompute_table();
1441  index_ivfpq->own_fields = storage2l->q1.own_fields;
1442  storage2l->transfer_to_IVFPQ(*index_ivfpq);
1443  index_ivfpq->make_direct_map (true);
1444 
1445  storage = index_ivfpq;
1446  delete storage2l;
1447 
1448 }
1449 
1450 
1451 } // namespace faiss
void precompute_table()
build precomputed table
Definition: IndexIVFPQ.cpp:364
void transfer_to_IVFPQ(IndexIVFPQ &other) const
transfer the flat codes to an IVFPQ index
void neighbor_range(idx_t no, int layer_no, size_t *begin, size_t *end) const
range of entries in the neighbors table of vertex no at layer_no
Definition: HNSW.cpp:43
virtual void search_preassigned(idx_t n, const float *x, idx_t k, const idx_t *assign, const float *centroid_dis, float *distances, idx_t *labels, bool store_pairs, const IVFSearchParameters *params=nullptr) const
Definition: IndexIVF.cpp:189
bool get(int no) const
get flag #no
Definition: HNSW.h:248
float fvec_L2sqr(const float *x, const float *y, size_t d)
Squared L2 distance between two vectors.
Definition: utils_simd.cpp:502
void train(idx_t n, const float *x) override
Trains the storage if needed.
Definition: IndexHNSW.cpp:979
virtual const idx_t * get_ids(size_t list_no) const =0
void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const override
entry point for search
Definition: IndexHNSW.cpp:1324
virtual void reset()=0
removes all elements from the database.
size_t nprobe
number of probes at query time
Definition: IndexIVF.h:98
virtual float symmetric_dis(storage_idx_t i, storage_idx_t j)=0
compute distance between two stored vectors
virtual void train(idx_t n, const float *x)
Definition: Index.cpp:24
Level1Quantizer q1
first level quantizer
Definition: IndexIVFPQ.h:207
void advance()
reset all flags to false
Definition: HNSW.h:253
void get_neighbor_table(storage_idx_t i, float *out) const
get the M+1 -by-d table for neighbor coordinates for vector i
Definition: IndexHNSW.cpp:748
void train(idx_t n, const float *x) override
Trains the storage if needed.
Definition: IndexHNSW.cpp:206
int d
vector dimension
Definition: Index.h:66
set implementation optimized for fast access.
Definition: HNSW.h:235
void add(idx_t n, const float *x) override
Definition: IndexHNSW.cpp:259
void reconstruct(storage_idx_t i, float *x, float *tmp) const
called by compute_distances
Definition: IndexHNSW.cpp:613
virtual void add(idx_t n, const float *x)=0
long idx_t
all indices are this type
Definition: Index.h:64
virtual void set_query(const float *x)=0
called before computing distances
idx_t ntotal
total nb of indexed vectors
Definition: Index.h:67
double getmillisecs()
ms elapsed since some arbitrary epoch
Definition: utils.cpp:70
void estimate_code(const float *x, storage_idx_t i, uint8_t *code) const
called by add_codes
Definition: IndexHNSW.cpp:767
virtual void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const =0
void add_codes(size_t n, const float *x)
Definition: IndexHNSW.cpp:807
void make_direct_map(bool new_maintain_direct_map=true)
Definition: IndexIVF.cpp:144
ProductQuantizer pq
produces the codes
Definition: IndexIVFPQ.h:38
InvertedLists * invlists
Acess to the actual data.
Definition: IndexIVF.h:93
void reset() override
removes all elements from the database.
Definition: IndexHNSW.cpp:270
size_t M
number of subquantizers
void init_level_0_from_entry_points(int npt, const storage_idx_t *points, const storage_idx_t *nearests)
alternative graph building
Definition: IndexHNSW.cpp:430
void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const override
entry point for search
Definition: IndexHNSW.cpp:213
Index * quantizer
quantizer that maps vectors to inverted lists
Definition: IndexIVF.h:33
void init_level_0_from_knngraph(int k, const float *D, const idx_t *I)
alternative graph building
Definition: IndexHNSW.cpp:392
void search_level_0(idx_t n, const float *x, idx_t k, const storage_idx_t *nearest, const float *nearest_d, float *distances, idx_t *labels, int nprobe=1, int search_type=1) const
Definition: IndexHNSW.cpp:320
bool is_trained
set if the Index does not require training, or if training is done already
Definition: Index.h:71
std::vector< storage_idx_t > neighbors
Definition: HNSW.h:134
void reconstruct(idx_t key, float *recons) const override
Definition: IndexHNSW.cpp:277
virtual void reconstruct(idx_t key, float *recons) const
Definition: Index.cpp:55
ProductQuantizer pq
second level quantizer is always a PQ
Definition: IndexIVFPQ.h:210
int storage_idx_t
internal storage of vectors (32 bits: this is expensive)
Definition: HNSW.h:49
bool own_fields
whether object owns the quantizer
Definition: IndexIVF.h:42
void set(int no)
set flog #no to true
Definition: HNSW.h:243
size_t nlist
number of possible key values
Definition: IndexIVF.h:34
static void shrink_neighbor_list(DistanceComputer &qdis, std::priority_queue< NodeDistFarther > &input, std::vector< NodeDistFarther > &output, int max_size)
Definition: HNSW.cpp:237