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