Faiss
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends
/data/users/matthijs/github_faiss/faiss/MetaIndexes.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 // Copyright 2004-present Facebook. All Rights Reserved
10 // -*- c++ -*-
11 
12 #include "MetaIndexes.h"
13 
14 #include <pthread.h>
15 
16 #include <cstdio>
17 
18 #include "FaissAssert.h"
19 #include "Heap.h"
20 #include "AuxIndexStructures.h"
21 
22 
23 namespace faiss {
24 
25 /*****************************************************
26  * IndexIDMap implementation
27  *******************************************************/
28 
29 IndexIDMap::IndexIDMap (Index *index):
30  index (index),
31  own_fields (false)
32 {
33  FAISS_THROW_IF_NOT_MSG (index->ntotal == 0, "index must be empty on input");
34  is_trained = index->is_trained;
35  metric_type = index->metric_type;
36  verbose = index->verbose;
37  d = index->d;
38 }
39 
40 void IndexIDMap::add (idx_t, const float *)
41 {
42  FAISS_THROW_MSG ("add does not make sense with IndexIDMap, "
43  "use add_with_ids");
44 }
45 
46 
47 void IndexIDMap::train (idx_t n, const float *x)
48 {
49  index->train (n, x);
50  is_trained = index->is_trained;
51 }
52 
54 {
55  index->reset ();
56  ntotal = 0;
57 }
58 
59 
60 void IndexIDMap::add_with_ids (idx_t n, const float * x, const long *xids)
61 {
62  index->add (n, x);
63  for (idx_t i = 0; i < n; i++)
64  id_map.push_back (xids[i]);
65  ntotal = index->ntotal;
66 }
67 
68 
69 void IndexIDMap::search (idx_t n, const float *x, idx_t k,
70  float *distances, idx_t *labels) const
71 {
72  index->search (n, x, k, distances, labels);
73  idx_t *li = labels;
74  for (idx_t i = 0; i < n * k; i++) {
75  li[i] = li[i] < 0 ? li[i] : id_map[li[i]];
76  }
77 }
78 
79 namespace {
80 
81 struct IDTranslatedSelector: IDSelector {
82  const std::vector <long> & id_map;
83  const IDSelector & sel;
84  IDTranslatedSelector (const std::vector <long> & id_map,
85  const IDSelector & sel):
86  id_map (id_map), sel (sel)
87  {}
88  bool is_member(idx_t id) const override {
89  return sel.is_member(id_map[id]);
90  }
91 };
92 
93 }
94 
96 {
97  // remove in sub-index first
98  IDTranslatedSelector sel2 (id_map, sel);
99  long nremove = index->remove_ids (sel2);
100 
101  long j = 0;
102  for (idx_t i = 0; i < ntotal; i++) {
103  if (sel.is_member (id_map[i])) {
104  // remove
105  } else {
106  id_map[j] = id_map[i];
107  j++;
108  }
109  }
110  FAISS_ASSERT (j == index->ntotal);
111  ntotal = j;
112  return nremove;
113 }
114 
115 
116 
117 
118 IndexIDMap::~IndexIDMap ()
119 {
120  if (own_fields) delete index;
121 }
122 
123 /*****************************************************
124  * IndexIDMap2 implementation
125  *******************************************************/
126 
127 IndexIDMap2::IndexIDMap2 (Index *index): IndexIDMap (index)
128 {}
129 
130 void IndexIDMap2::add_with_ids(idx_t n, const float* x, const long* xids)
131 {
132  size_t prev_ntotal = ntotal;
133  IndexIDMap::add_with_ids (n, x, xids);
134  for (size_t i = prev_ntotal; i < ntotal; i++) {
135  rev_map [id_map [i]] = i;
136  }
137 }
138 
140 {
141  rev_map.clear ();
142  for (size_t i = 0; i < ntotal; i++) {
143  rev_map [id_map [i]] = i;
144  }
145 }
146 
147 
149 {
150  // This is quite inefficient
151  long nremove = IndexIDMap::remove_ids (sel);
153  return nremove;
154 }
155 
156 void IndexIDMap2::reconstruct (idx_t key, float * recons) const
157 {
158  try {
159  index->reconstruct (rev_map.at (key), recons);
160  } catch (const std::out_of_range& e) {
161  FAISS_THROW_FMT ("key %ld not found", key);
162  }
163 }
164 
165 
166 
167 /*****************************************************
168  * IndexShards implementation
169  *******************************************************/
170 
171 // subroutines
172 namespace {
173 
174 
175 typedef Index::idx_t idx_t;
176 
177 
178 template<class Job>
179 struct Thread {
180  Job job;
181  pthread_t thread;
182 
183  Thread () {}
184 
185  explicit Thread (const Job & job): job(job) {}
186 
187  void start () {
188  pthread_create (&thread, nullptr, run, this);
189  }
190 
191  void wait () {
192  pthread_join (thread, nullptr);
193  }
194 
195  static void * run (void *arg) {
196  static_cast<Thread*> (arg)->job.run();
197  return nullptr;
198  }
199 
200 };
201 
202 
203 /// callback + thread management to train 1 shard
204 struct TrainJob {
205  IndexShards *index; // the relevant index
206  int no; // shard number
207  idx_t n; // train points
208  const float *x;
209 
210  void run ()
211  {
212  if (index->verbose)
213  printf ("begin train shard %d on %ld points\n", no, n);
214  index->shard_indexes [no]->train(n, x);
215  if (index->verbose)
216  printf ("end train shard %d\n", no);
217  }
218 
219 };
220 
221 struct AddJob {
222  IndexShards *index; // the relevant index
223  int no; // shard number
224 
225  idx_t n;
226  const float *x;
227  const idx_t *ids;
228 
229  void run ()
230  {
231  if (index->verbose)
232  printf ("begin add shard %d on %ld points\n", no, n);
233  if (ids)
234  index->shard_indexes[no]->add_with_ids (n, x, ids);
235  else
236  index->shard_indexes[no]->add (n, x);
237  if (index->verbose)
238  printf ("end add shard %d on %ld points\n", no, n);
239  }
240 };
241 
242 
243 
244 /// callback + thread management to query in 1 shard
245 struct QueryJob {
246  const IndexShards *index; // the relevant index
247  int no; // shard number
248 
249  // query params
250  idx_t n;
251  const float *x;
252  idx_t k;
253  float *distances;
254  idx_t *labels;
255 
256 
257  void run ()
258  {
259  if (index->verbose)
260  printf ("begin query shard %d on %ld points\n", no, n);
261  index->shard_indexes [no]->search (n, x, k,
262  distances, labels);
263  if (index->verbose)
264  printf ("end query shard %d\n", no);
265  }
266 
267 
268 };
269 
270 
271 
272 
273 // add translation to all valid labels
274 void translate_labels (long n, idx_t *labels, long translation)
275 {
276  if (translation == 0) return;
277  for (long i = 0; i < n; i++) {
278  if(labels[i] < 0) return;
279  labels[i] += translation;
280  }
281 }
282 
283 
284 /** merge result tables from several shards.
285  * @param all_distances size nshard * n * k
286  * @param all_labels idem
287  * @param translartions label translations to apply, size nshard
288  */
289 
290 template <class C>
291 void merge_tables (long n, long k, long nshard,
292  float *distances, idx_t *labels,
293  const float *all_distances,
294  idx_t *all_labels,
295  const long *translations)
296 {
297  if(k == 0) {
298  return;
299  }
300 
301  long stride = n * k;
302 #pragma omp parallel
303  {
304  std::vector<int> buf (2 * nshard);
305  int * pointer = buf.data();
306  int * shard_ids = pointer + nshard;
307  std::vector<float> buf2 (nshard);
308  float * heap_vals = buf2.data();
309 #pragma omp for
310  for (long i = 0; i < n; i++) {
311  // the heap maps values to the shard where they are
312  // produced.
313  const float *D_in = all_distances + i * k;
314  const idx_t *I_in = all_labels + i * k;
315  int heap_size = 0;
316 
317  for (long s = 0; s < nshard; s++) {
318  pointer[s] = 0;
319  if (I_in[stride * s] >= 0)
320  heap_push<C> (++heap_size, heap_vals, shard_ids,
321  D_in[stride * s], s);
322  }
323 
324  float *D = distances + i * k;
325  idx_t *I = labels + i * k;
326 
327  for (int j = 0; j < k; j++) {
328  if (heap_size == 0) {
329  I[j] = -1;
330  D[j] = C::neutral();
331  } else {
332  // pop best element
333  int s = shard_ids[0];
334  int & p = pointer[s];
335  D[j] = heap_vals[0];
336  I[j] = I_in[stride * s + p] + translations[s];
337 
338  heap_pop<C> (heap_size--, heap_vals, shard_ids);
339  p++;
340  if (p < k && I_in[stride * s + p] >= 0)
341  heap_push<C> (++heap_size, heap_vals, shard_ids,
342  D_in[stride * s + p], s);
343  }
344  }
345  }
346  }
347 }
348 
349 
350 };
351 
352 
353 
354 
355 IndexShards::IndexShards (idx_t d, bool threaded, bool successive_ids):
356  Index (d), own_fields (false),
357  threaded (threaded), successive_ids (successive_ids)
358 {
359 
360 }
361 
362 void IndexShards::add_shard (Index *idx)
363 {
364  shard_indexes.push_back (idx);
365  sync_with_shard_indexes ();
366 }
367 
368 void IndexShards::sync_with_shard_indexes ()
369 {
370  if (shard_indexes.empty()) return;
371  Index * index0 = shard_indexes[0];
372  d = index0->d;
373  metric_type = index0->metric_type;
374  is_trained = index0->is_trained;
375  ntotal = index0->ntotal;
376  for (int i = 1; i < shard_indexes.size(); i++) {
377  Index * index = shard_indexes[i];
378  FAISS_THROW_IF_NOT (metric_type == index->metric_type);
379  FAISS_THROW_IF_NOT (d == index->d);
380  ntotal += index->ntotal;
381  }
382 }
383 
384 
385 void IndexShards::train (idx_t n, const float *x)
386 {
387 
388  // pre-alloc because we don't want reallocs
389  std::vector<Thread<TrainJob > > tss (shard_indexes.size());
390  int nt = 0;
391  for (int i = 0; i < shard_indexes.size(); i++) {
392  if(!shard_indexes[i]->is_trained) {
393  TrainJob ts = {this, i, n, x};
394  if (threaded) {
395  tss[nt] = Thread<TrainJob> (ts);
396  tss[nt++].start();
397  } else {
398  ts.run();
399  }
400  }
401  }
402  for (int i = 0; i < nt; i++) {
403  tss[i].wait();
404  }
405  sync_with_shard_indexes ();
406 }
407 
408 void IndexShards::add (idx_t n, const float *x)
409 {
410  add_with_ids (n, x, nullptr);
411 }
412 
413  /**
414  * Cases (successive_ids, xids):
415  * - true, non-NULL ERROR: it makes no sense to pass in ids and
416  * request them to be shifted
417  * - true, NULL OK, but should be called only once (calls add()
418  * on sub-indexes).
419  * - false, non-NULL OK: will call add_with_ids with passed in xids
420  * distributed evenly over shards
421  * - false, NULL OK: will call add_with_ids on each sub-index,
422  * starting at ntotal
423  */
424 
425 void IndexShards::add_with_ids (idx_t n, const float * x, const long *xids)
426 {
427 
428  FAISS_THROW_IF_NOT_MSG(!(successive_ids && xids),
429  "It makes no sense to pass in ids and "
430  "request them to be shifted");
431 
432  if (successive_ids) {
433  FAISS_THROW_IF_NOT_MSG(!xids,
434  "It makes no sense to pass in ids and "
435  "request them to be shifted");
436  FAISS_THROW_IF_NOT_MSG(ntotal == 0,
437  "when adding to IndexShards with sucessive_ids, "
438  "only add() in a single pass is supported");
439  }
440 
441  long nshard = shard_indexes.size();
442  const long *ids = xids;
443  ScopeDeleter<long> del;
444  if (!ids && !successive_ids) {
445  long *aids = new long[n];
446  for (long i = 0; i < n; i++)
447  aids[i] = ntotal + i;
448  ids = aids;
449  del.set (ids);
450  }
451 
452  std::vector<Thread<AddJob > > asa (shard_indexes.size());
453  int nt = 0;
454  for (int i = 0; i < nshard; i++) {
455  long i0 = i * n / nshard;
456  long i1 = (i + 1) * n / nshard;
457 
458  AddJob as = {this, i,
459  i1 - i0, x + i0 * d,
460  ids ? ids + i0 : nullptr};
461  if (threaded) {
462  asa[nt] = Thread<AddJob>(as);
463  asa[nt++].start();
464  } else {
465  as.run();
466  }
467  }
468  for (int i = 0; i < nt; i++) {
469  asa[i].wait();
470  }
471  ntotal += n;
472 }
473 
474 
475 
476 
477 
479 {
480  for (int i = 0; i < shard_indexes.size(); i++) {
481  shard_indexes[i]->reset ();
482  }
483  sync_with_shard_indexes ();
484 }
485 
487  idx_t n, const float *x, idx_t k,
488  float *distances, idx_t *labels) const
489 {
490  long nshard = shard_indexes.size();
491  float *all_distances = new float [nshard * k * n];
492  idx_t *all_labels = new idx_t [nshard * k * n];
493  ScopeDeleter<float> del (all_distances);
494  ScopeDeleter<idx_t> del2 (all_labels);
495 
496 #if 1
497 
498  // pre-alloc because we don't want reallocs
499  std::vector<Thread<QueryJob> > qss (nshard);
500  for (int i = 0; i < nshard; i++) {
501  QueryJob qs = {
502  this, i, n, x, k,
503  all_distances + i * k * n,
504  all_labels + i * k * n
505  };
506  if (threaded) {
507  qss[i] = Thread<QueryJob> (qs);
508  qss[i].start();
509  } else {
510  qs.run();
511  }
512  }
513 
514  if (threaded) {
515  for (int i = 0; i < qss.size(); i++) {
516  qss[i].wait();
517  }
518  }
519 #else
520 
521  // pre-alloc because we don't want reallocs
522  std::vector<QueryJob> qss (nshard);
523  for (int i = 0; i < nshard; i++) {
524  QueryJob qs = {
525  this, i, n, x, k,
526  all_distances + i * k * n,
527  all_labels + i * k * n
528  };
529  if (threaded) {
530  qss[i] = qs;
531  } else {
532  qs.run();
533  }
534  }
535 
536  if (threaded) {
537 #pragma omp parallel for
538  for (int i = 0; i < qss.size(); i++) {
539  qss[i].run();
540  }
541  }
542 
543 #endif
544  std::vector<long> translations (nshard, 0);
545  if (successive_ids) {
546  translations[0] = 0;
547  for (int s = 0; s + 1 < nshard; s++)
548  translations [s + 1] = translations [s] +
549  shard_indexes [s]->ntotal;
550  }
551 
552  if (metric_type == METRIC_L2) {
553  merge_tables< CMin<float, int> > (
554  n, k, nshard, distances, labels,
555  all_distances, all_labels, translations.data ());
556  } else {
557  merge_tables< CMax<float, int> > (
558  n, k, nshard, distances, labels,
559  all_distances, all_labels, translations.data ());
560  }
561 
562 }
563 
564 
565 
566 IndexShards::~IndexShards ()
567 {
568  if (own_fields) {
569  for (int s = 0; s < shard_indexes.size(); s++)
570  delete shard_indexes [s];
571  }
572 }
573 
574 
575 /*****************************************************
576  * IndexSplitVectors implementation
577  *******************************************************/
578 
579 
580 IndexSplitVectors::IndexSplitVectors (idx_t d, bool threaded):
581  Index (d), own_fields (false),
582  threaded (threaded), sum_d (0)
583 {
584 
585 }
586 
587 void IndexSplitVectors::add_sub_index (Index *index)
588 {
589  sub_indexes.push_back (index);
590  sync_with_sub_indexes ();
591 }
592 
593 void IndexSplitVectors::sync_with_sub_indexes ()
594 {
595  if (sub_indexes.empty()) return;
596  Index * index0 = sub_indexes[0];
597  sum_d = index0->d;
598  metric_type = index0->metric_type;
599  is_trained = index0->is_trained;
600  ntotal = index0->ntotal;
601  for (int i = 1; i < sub_indexes.size(); i++) {
602  Index * index = sub_indexes[i];
603  FAISS_THROW_IF_NOT (metric_type == index->metric_type);
604  FAISS_THROW_IF_NOT (ntotal == index->ntotal);
605  sum_d += index->d;
606  }
607 
608 }
609 
610 void IndexSplitVectors::add(idx_t /*n*/, const float* /*x*/) {
611  FAISS_THROW_MSG("not implemented");
612 }
613 
614 namespace {
615 
616 /// callback + thread management to query in 1 shard
617 struct SplitQueryJob {
618  const IndexSplitVectors *index; // the relevant index
619  int no; // shard number
620 
621  // query params
622  idx_t n;
623  const float *x;
624  idx_t k;
625  float *distances;
626  idx_t *labels;
627 
628 
629  void run ()
630  {
631  if (index->verbose)
632  printf ("begin query shard %d on %ld points\n", no, n);
633  const Index * sub_index = index->sub_indexes[no];
634  long sub_d = sub_index->d, d = index->d;
635  idx_t ofs = 0;
636  for (int i = 0; i < no; i++) ofs += index->sub_indexes[i]->d;
637  float *sub_x = new float [sub_d * n];
638  ScopeDeleter<float> del (sub_x);
639  for (idx_t i = 0; i < n; i++)
640  memcpy (sub_x + i * sub_d, x + ofs + i * d, sub_d * sizeof (sub_x));
641  sub_index->search (n, sub_x, k, distances, labels);
642  if (index->verbose)
643  printf ("end query shard %d\n", no);
644  }
645 
646 };
647 
648 
649 
650 }
651 
652 
653 
655  idx_t n, const float *x, idx_t k,
656  float *distances, idx_t *labels) const
657 {
658  FAISS_THROW_IF_NOT_MSG (k == 1,
659  "search implemented only for k=1");
660  FAISS_THROW_IF_NOT_MSG (sum_d == d,
661  "not enough indexes compared to # dimensions");
662 
663  long nshard = sub_indexes.size();
664  float *all_distances = new float [nshard * k * n];
665  idx_t *all_labels = new idx_t [nshard * k * n];
666  ScopeDeleter<float> del (all_distances);
667  ScopeDeleter<idx_t> del2 (all_labels);
668 
669  // pre-alloc because we don't want reallocs
670  std::vector<Thread<SplitQueryJob> > qss (nshard);
671  for (int i = 0; i < nshard; i++) {
672  SplitQueryJob qs = {
673  this, i, n, x, k,
674  i == 0 ? distances : all_distances + i * k * n,
675  i == 0 ? labels : all_labels + i * k * n
676  };
677  if (threaded) {
678  qss[i] = Thread<SplitQueryJob> (qs);
679  qss[i].start();
680  } else {
681  qs.run();
682  }
683  }
684 
685  if (threaded) {
686  for (int i = 0; i < qss.size(); i++) {
687  qss[i].wait();
688  }
689  }
690 
691  long factor = 1;
692  for (int i = 0; i < nshard; i++) {
693  if (i > 0) { // results of 0 are already in the table
694  const float *distances_i = all_distances + i * k * n;
695  const idx_t *labels_i = all_labels + i * k * n;
696  for (long j = 0; j < n; j++) {
697  if (labels[j] >= 0 && labels_i[j] >= 0) {
698  labels[j] += labels_i[j] * factor;
699  distances[j] += distances_i[j];
700  } else {
701  labels[j] = -1;
702  distances[j] = 0.0 / 0.0;
703  }
704  }
705  }
706  factor *= sub_indexes[i]->ntotal;
707  }
708 
709 }
710 
711 void IndexSplitVectors::train(idx_t /*n*/, const float* /*x*/) {
712  FAISS_THROW_MSG("not implemented");
713 }
714 
716 {
717  FAISS_THROW_MSG ("not implemented");
718 }
719 
720 
721 IndexSplitVectors::~IndexSplitVectors ()
722 {
723  if (own_fields) {
724  for (int s = 0; s < sub_indexes.size(); s++)
725  delete sub_indexes [s];
726  }
727 }
728 
729 
730 
731 
732 
733 
734 }; // namespace faiss
void train(idx_t n, const float *x) override
IndexShards(idx_t d, bool threaded=false, bool successive_ids=true)
virtual void reset()=0
removes all elements from the database.
virtual void train(idx_t, const float *)
Definition: Index.h:89
void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const override
Definition: MetaIndexes.cpp:69
void add_with_ids(idx_t n, const float *x, const long *xids) override
void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const override
void add_with_ids(idx_t n, const float *x, const long *xids) override
void reset() override
removes all elements from the database.
void add(idx_t n, const float *x) override
this will fail. Use add_with_ids
Definition: MetaIndexes.cpp:40
int d
vector dimension
Definition: Index.h:64
std::vector< long > id_map
! whether pointers are deleted in destructo
Definition: MetaIndexes.h:29
virtual void add(idx_t n, const float *x)=0
void train(idx_t n, const float *x) override
void add(idx_t n, const float *x) override
supported only for sub-indices that implement add_with_ids
long idx_t
all indices are this type
Definition: Index.h:62
idx_t ntotal
total nb of indexed vectors
Definition: Index.h:65
void construct_rev_map()
make the rev_map from scratch
bool verbose
verbosity level
Definition: Index.h:66
void add(idx_t n, const float *x) override
virtual long remove_ids(const IDSelector &sel)
Definition: Index.cpp:37
long remove_ids(const IDSelector &sel) override
remove ids adapted to IndexFlat
bool threaded
should the sub-indexes be deleted along with this?
Definition: MetaIndexes.h:87
virtual void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const =0
void reset() override
removes all elements from the database.
void reset() override
removes all elements from the database.
Definition: MetaIndexes.cpp:53
void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const override
long remove_ids(const IDSelector &sel) override
remove ids adapted to IndexFlat
Definition: MetaIndexes.cpp:95
MetricType metric_type
type of metric this index uses for search
Definition: Index.h:72
void reconstruct(idx_t key, float *recons) const override
void train(idx_t n, const float *x) override
Definition: MetaIndexes.cpp:47
bool is_trained
set if the Index does not require training, or if training is done already
Definition: Index.h:69
virtual void reconstruct(idx_t key, float *recons) const
Definition: Index.cpp:43
void add_with_ids(idx_t n, const float *x, const long *xids) override
Definition: MetaIndexes.cpp:60
IndexSplitVectors(idx_t d, bool threaded=false)
sum of dimensions seen so far
bool own_fields
! the sub-index
Definition: MetaIndexes.h:28