Faiss
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends
/tmp/faiss/AutoTune.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 /*
12  * implementation of Hyper-parameter auto-tuning
13  */
14 
15 #include "AutoTune.h"
16 
17 #include <cmath>
18 
19 #include "FaissAssert.h"
20 #include "utils.h"
21 
22 #include "IndexFlat.h"
23 #include "VectorTransform.h"
24 #include "IndexLSH.h"
25 #include "IndexPQ.h"
26 #include "IndexIVF.h"
27 #include "IndexIVFPQ.h"
28 #include "IndexIVFFlat.h"
29 #include "MetaIndexes.h"
30 #include "IndexScalarQuantizer.h"
31 #include "IndexHNSW.h"
32 #include "IndexBinaryFlat.h"
33 #include "IndexBinaryHNSW.h"
34 #include "IndexBinaryIVF.h"
35 
36 namespace faiss {
37 
38 
39 AutoTuneCriterion::AutoTuneCriterion (idx_t nq, idx_t nnn):
40  nq (nq), nnn (nnn), gt_nnn (0)
41 {}
42 
43 
45  int gt_nnn, const float *gt_D_in, const idx_t *gt_I_in)
46 {
47  this->gt_nnn = gt_nnn;
48  if (gt_D_in) { // allow null for this, as it is often not used
49  gt_D.resize (nq * gt_nnn);
50  memcpy (gt_D.data(), gt_D_in, sizeof (gt_D[0]) * nq * gt_nnn);
51  }
52  gt_I.resize (nq * gt_nnn);
53  memcpy (gt_I.data(), gt_I_in, sizeof (gt_I[0]) * nq * gt_nnn);
54 }
55 
56 
57 
58 OneRecallAtRCriterion::OneRecallAtRCriterion (idx_t nq, idx_t R):
59  AutoTuneCriterion(nq, R), R(R)
60 {}
61 
62 double OneRecallAtRCriterion::evaluate(const float* /*D*/, const idx_t* I)
63  const {
64  FAISS_THROW_IF_NOT_MSG(
65  (gt_I.size() == gt_nnn * nq && gt_nnn >= 1 && nnn >= R),
66  "ground truth not initialized");
67  idx_t n_ok = 0;
68  for (idx_t q = 0; q < nq; q++) {
69  idx_t gt_nn = gt_I[q * gt_nnn];
70  const idx_t* I_line = I + q * nnn;
71  for (int i = 0; i < R; i++) {
72  if (I_line[i] == gt_nn) {
73  n_ok++;
74  break;
75  }
76  }
77  }
78  return n_ok / double(nq);
79 }
80 
81 
82 IntersectionCriterion::IntersectionCriterion (idx_t nq, idx_t R):
83  AutoTuneCriterion(nq, R), R(R)
84 {}
85 
86 double IntersectionCriterion::evaluate(const float* /*D*/, const idx_t* I)
87  const {
88  FAISS_THROW_IF_NOT_MSG(
89  (gt_I.size() == gt_nnn * nq && gt_nnn >= R && nnn >= R),
90  "ground truth not initialized");
91  long n_ok = 0;
92 #pragma omp parallel for reduction(+: n_ok)
93  for (idx_t q = 0; q < nq; q++) {
95  R, &gt_I [q * gt_nnn],
96  R, I + q * nnn);
97  }
98  return n_ok / double (nq * R);
99 }
100 
101 /***************************************************************
102  * OperatingPoints
103  ***************************************************************/
104 
105 OperatingPoints::OperatingPoints ()
106 {
107  clear();
108 }
109 
111 {
112  all_pts.clear();
113  optimal_pts.clear();
114  /// default point: doing nothing gives 0 performance and takes 0 time
115  OperatingPoint op = {0, 0, "", -1};
116  optimal_pts.push_back(op);
117 }
118 
119 /// add a performance measure
120 bool OperatingPoints::add (double perf, double t, const std::string & key,
121  size_t cno)
122 {
123  OperatingPoint op = {perf, t, key, long(cno)};
124  all_pts.push_back (op);
125  if (perf == 0) {
126  return false; // no method for 0 accuracy is faster than doing nothing
127  }
128  std::vector<OperatingPoint> & a = optimal_pts;
129  if (perf > a.back().perf) {
130  // keep unconditionally
131  a.push_back (op);
132  } else if (perf == a.back().perf) {
133  if (t < a.back ().t) {
134  a.back() = op;
135  } else {
136  return false;
137  }
138  } else {
139  int i;
140  // stricto sensu this should be a bissection
141  for (i = 0; i < a.size(); i++) {
142  if (a[i].perf >= perf) break;
143  }
144  assert (i < a.size());
145  if (t < a[i].t) {
146  if (a[i].perf == perf) {
147  a[i] = op;
148  } else {
149  a.insert (a.begin() + i, op);
150  }
151  } else {
152  return false;
153  }
154  }
155  { // remove non-optimal points from array
156  int i = a.size() - 1;
157  while (i > 0) {
158  if (a[i].t < a[i - 1].t)
159  a.erase (a.begin() + (i - 1));
160  i--;
161  }
162  }
163  return true;
164 }
165 
166 
168  const std::string & prefix)
169 {
170  int n_add = 0;
171  for (int i = 0; i < other.all_pts.size(); i++) {
172  const OperatingPoint & op = other.all_pts[i];
173  if (add (op.perf, op.t, prefix + op.key, op.cno))
174  n_add++;
175  }
176  return n_add;
177 }
178 
179 
180 
181 /// get time required to obtain a given performance measure
182 double OperatingPoints::t_for_perf (double perf) const
183 {
184  const std::vector<OperatingPoint> & a = optimal_pts;
185  if (perf > a.back().perf) return 1e50;
186  int i0 = -1, i1 = a.size() - 1;
187  while (i0 + 1 < i1) {
188  int imed = (i0 + i1 + 1) / 2;
189  if (a[imed].perf < perf) i0 = imed;
190  else i1 = imed;
191  }
192  return a[i1].t;
193 }
194 
195 
196 void OperatingPoints::all_to_gnuplot (const char *fname) const
197 {
198  FILE *f = fopen(fname, "w");
199  if (!f) {
200  fprintf (stderr, "cannot open %s", fname);
201  perror("");
202  abort();
203  }
204  for (int i = 0; i < all_pts.size(); i++) {
205  const OperatingPoint & op = all_pts[i];
206  fprintf (f, "%g %g %s\n", op.perf, op.t, op.key.c_str());
207  }
208  fclose(f);
209 }
210 
211 void OperatingPoints::optimal_to_gnuplot (const char *fname) const
212 {
213  FILE *f = fopen(fname, "w");
214  if (!f) {
215  fprintf (stderr, "cannot open %s", fname);
216  perror("");
217  abort();
218  }
219  double prev_perf = 0.0;
220  for (int i = 0; i < optimal_pts.size(); i++) {
221  const OperatingPoint & op = optimal_pts[i];
222  fprintf (f, "%g %g\n", prev_perf, op.t);
223  fprintf (f, "%g %g %s\n", op.perf, op.t, op.key.c_str());
224  prev_perf = op.perf;
225  }
226  fclose(f);
227 }
228 
229 void OperatingPoints::display (bool only_optimal) const
230 {
231  const std::vector<OperatingPoint> &pts =
232  only_optimal ? optimal_pts : all_pts;
233  printf("Tested %ld operating points, %ld ones are optimal:\n",
234  all_pts.size(), optimal_pts.size());
235 
236  for (int i = 0; i < pts.size(); i++) {
237  const OperatingPoint & op = pts[i];
238  const char *star = "";
239  if (!only_optimal) {
240  for (int j = 0; j < optimal_pts.size(); j++) {
241  if (op.cno == optimal_pts[j].cno) {
242  star = "*";
243  break;
244  }
245  }
246  }
247  printf ("cno=%ld key=%s perf=%.4f t=%.3f %s\n",
248  op.cno, op.key.c_str(), op.perf, op.t, star);
249  }
250 
251 }
252 
253 /***************************************************************
254  * ParameterSpace
255  ***************************************************************/
256 
257 ParameterSpace::ParameterSpace ():
258  verbose (1), n_experiments (500),
259  batchsize (1<<30), thread_over_batches (false),
260  min_test_duration (0)
261 {
262 }
263 
264 /* not keeping this constructor as inheritors will call the parent
265  initialize()
266  */
267 
268 #if 0
269 ParameterSpace::ParameterSpace (Index *index):
270  verbose (1), n_experiments (500),
271  batchsize (1<<30), thread_over_batches (false)
272 
273 {
274  initialize(index);
275 }
276 #endif
277 
279 {
280  size_t n = 1;
281  for (int i = 0; i < parameter_ranges.size(); i++)
282  n *= parameter_ranges[i].values.size();
283  return n;
284 }
285 
286 /// get string representation of the combination
287 std::string ParameterSpace::combination_name (size_t cno) const {
288  char buf[1000], *wp = buf;
289  *wp = 0;
290  for (int i = 0; i < parameter_ranges.size(); i++) {
291  const ParameterRange & pr = parameter_ranges[i];
292  size_t j = cno % pr.values.size();
293  cno /= pr.values.size();
294  wp += snprintf (
295  wp, buf + 1000 - wp, "%s%s=%g", i == 0 ? "" : ",",
296  pr.name.c_str(), pr.values[j]);
297  }
298  return std::string (buf);
299 }
300 
301 
302 bool ParameterSpace::combination_ge (size_t c1, size_t c2) const
303 {
304  for (int i = 0; i < parameter_ranges.size(); i++) {
305  int nval = parameter_ranges[i].values.size();
306  size_t j1 = c1 % nval;
307  size_t j2 = c2 % nval;
308  if (!(j1 >= j2)) return false;
309  c1 /= nval;
310  c2 /= nval;
311  }
312  return true;
313 }
314 
315 
316 
317 #define DC(classname) \
318  const classname *ix = dynamic_cast<const classname *>(index)
319 
320 static void init_pq_ParameterRange (const ProductQuantizer & pq,
321  ParameterRange & pr)
322 {
323  if (pq.code_size % 4 == 0) {
324  // Polysemous not supported for code sizes that are not a
325  // multiple of 4
326  for (int i = 2; i <= pq.code_size * 8 / 2; i+= 2)
327  pr.values.push_back(i);
328  }
329  pr.values.push_back (pq.code_size * 8);
330 }
331 
333 {
334  for (auto & pr : parameter_ranges) {
335  if (pr.name == name) {
336  return pr;
337  }
338  }
339  parameter_ranges.push_back (ParameterRange ());
340  parameter_ranges.back ().name = name;
341  return parameter_ranges.back ();
342 }
343 
344 
345 /// initialize with reasonable parameters for the index
346 void ParameterSpace::initialize (const Index * index)
347 {
348  if (DC (IndexPreTransform)) {
349  index = ix->index;
350  }
351  if (DC (IndexRefineFlat)) {
352  ParameterRange & pr = add_range("k_factor_rf");
353  for (int i = 0; i <= 6; i++) {
354  pr.values.push_back (1 << i);
355  }
356  index = ix->base_index;
357  }
358  if (DC (IndexPreTransform)) {
359  index = ix->index;
360  }
361 
362  if (DC (IndexIVF)) {
363  {
364  ParameterRange & pr = add_range("nprobe");
365  for (int i = 0; i < 13; i++) {
366  size_t nprobe = 1 << i;
367  if (nprobe >= ix->nlist) break;
368  pr.values.push_back (nprobe);
369  }
370  }
371  if (dynamic_cast<const IndexHNSW*>(ix->quantizer)) {
372  ParameterRange & pr = add_range("efSearch");
373  for (int i = 2; i <= 9; i++) {
374  pr.values.push_back (1 << i);
375  }
376  }
377  }
378  if (DC (IndexPQ)) {
379  ParameterRange & pr = add_range("ht");
380  init_pq_ParameterRange (ix->pq, pr);
381  }
382  if (DC (IndexIVFPQ)) {
383  ParameterRange & pr = add_range("ht");
384  init_pq_ParameterRange (ix->pq, pr);
385  }
386 
387  if (DC (IndexIVF)) {
388  const MultiIndexQuantizer *miq =
389  dynamic_cast<const MultiIndexQuantizer *> (ix->quantizer);
390  if (miq) {
391  ParameterRange & pr_max_codes = add_range("max_codes");
392  for (int i = 8; i < 20; i++) {
393  pr_max_codes.values.push_back (1 << i);
394  }
395  pr_max_codes.values.push_back (1.0 / 0.0);
396  }
397  }
398  if (DC (IndexIVFPQR)) {
399  ParameterRange & pr = add_range("k_factor");
400  for (int i = 0; i <= 6; i++) {
401  pr.values.push_back (1 << i);
402  }
403  }
404  if (dynamic_cast<const IndexHNSW*>(index)) {
405  ParameterRange & pr = add_range("efSearch");
406  for (int i = 2; i <= 9; i++) {
407  pr.values.push_back (1 << i);
408  }
409  }
410 }
411 
412 #undef DC
413 
414 // non-const version
415 #define DC(classname) classname *ix = dynamic_cast<classname *>(index)
416 
417 
418 /// set a combination of parameters on an index
419 void ParameterSpace::set_index_parameters (Index *index, size_t cno) const
420 {
421 
422  for (int i = 0; i < parameter_ranges.size(); i++) {
423  const ParameterRange & pr = parameter_ranges[i];
424  size_t j = cno % pr.values.size();
425  cno /= pr.values.size();
426  double val = pr.values [j];
427  set_index_parameter (index, pr.name, val);
428  }
429 }
430 
431 /// set a combination of parameters on an index
433  Index *index, const char *description_in) const
434 {
435  char description[strlen(description_in) + 1];
436  char *ptr;
437  memcpy (description, description_in, strlen(description_in) + 1);
438 
439  for (char *tok = strtok_r (description, " ,", &ptr);
440  tok;
441  tok = strtok_r (nullptr, " ,", &ptr)) {
442  char name[100];
443  double val;
444  int ret = sscanf (tok, "%100[^=]=%lf", name, &val);
445  FAISS_THROW_IF_NOT_FMT (
446  ret == 2, "could not interpret parameters %s", tok);
447  set_index_parameter (index, name, val);
448  }
449 
450 }
451 
453  Index * index, const std::string & name, double val) const
454 {
455  if (verbose > 1)
456  printf(" set %s=%g\n", name.c_str(), val);
457 
458  if (name == "verbose") {
459  index->verbose = int(val);
460  // and fall through to also enable it on sub-indexes
461  }
462  if (DC (IndexPreTransform)) {
463  set_index_parameter (ix->index, name, val);
464  return;
465  }
466  if (DC (IndexShards)) {
467  // call on all sub-indexes
468  for (auto & shard_index : ix->shard_indexes) {
469  set_index_parameter (shard_index, name, val);
470  }
471  return;
472  }
473  if (DC (IndexRefineFlat)) {
474  if (name == "k_factor_rf") {
475  ix->k_factor = int(val);
476  return;
477  }
478  // otherwise it is for the sub-index
479  set_index_parameter (&ix->refine_index, name, val);
480  return;
481  }
482 
483  if (name == "verbose") {
484  index->verbose = int(val);
485  return; // last verbose that we could find
486  }
487 
488  if (name == "nprobe") {
489  if ( DC(IndexIVF)) {
490  ix->nprobe = int(val);
491  return;
492  }
493  }
494 
495  if (name == "ht") {
496  if (DC (IndexPQ)) {
497  if (val >= ix->pq.code_size * 8) {
498  ix->search_type = IndexPQ::ST_PQ;
499  } else {
500  ix->search_type = IndexPQ::ST_polysemous;
501  ix->polysemous_ht = int(val);
502  }
503  return;
504  } else if (DC (IndexIVFPQ)) {
505  if (val >= ix->pq.code_size * 8) {
506  ix->polysemous_ht = 0;
507  } else {
508  ix->polysemous_ht = int(val);
509  }
510  return;
511  }
512  }
513 
514  if (name == "k_factor") {
515  if (DC (IndexIVFPQR)) {
516  ix->k_factor = val;
517  return;
518  }
519  }
520  if (name == "max_codes") {
521  if (DC (IndexIVF)) {
522  ix->max_codes = std::isfinite(val) ? size_t(val) : 0;
523  return;
524  }
525  }
526 
527  if (name == "efSearch") {
528  if (DC (IndexHNSW)) {
529  ix->hnsw.efSearch = int(val);
530  return;
531  }
532  if (DC (IndexIVF)) {
533  if (IndexHNSW *cq =
534  dynamic_cast<IndexHNSW *>(ix->quantizer)) {
535  cq->hnsw.efSearch = int(val);
536  return;
537  }
538  }
539  }
540 
541  FAISS_THROW_FMT ("ParameterSpace::set_index_parameter:"
542  "could not set parameter %s",
543  name.c_str());
544 }
545 
547 {
548  printf ("ParameterSpace, %ld parameters, %ld combinations:\n",
549  parameter_ranges.size (), n_combinations ());
550  for (int i = 0; i < parameter_ranges.size(); i++) {
551  const ParameterRange & pr = parameter_ranges[i];
552  printf (" %s: ", pr.name.c_str ());
553  char sep = '[';
554  for (int j = 0; j < pr.values.size(); j++) {
555  printf ("%c %g", sep, pr.values [j]);
556  sep = ',';
557  }
558  printf ("]\n");
559  }
560 }
561 
562 
563 
564 void ParameterSpace::update_bounds (size_t cno, const OperatingPoint & op,
565  double *upper_bound_perf,
566  double *lower_bound_t) const
567 {
568  if (combination_ge (cno, op.cno)) {
569  if (op.t > *lower_bound_t) *lower_bound_t = op.t;
570  }
571  if (combination_ge (op.cno, cno)) {
572  if (op.perf < *upper_bound_perf) *upper_bound_perf = op.perf;
573  }
574 }
575 
576 
577 
579  size_t nq, const float *xq,
580  const AutoTuneCriterion & crit,
581  OperatingPoints * ops) const
582 {
583  FAISS_THROW_IF_NOT_MSG (nq == crit.nq,
584  "criterion does not have the same nb of queries");
585 
586  size_t n_comb = n_combinations ();
587 
588  if (n_experiments == 0) {
589 
590  for (size_t cno = 0; cno < n_comb; cno++) {
591  set_index_parameters (index, cno);
592  std::vector<Index::idx_t> I(nq * crit.nnn);
593  std::vector<float> D(nq * crit.nnn);
594 
595  double t0 = getmillisecs ();
596  index->search (nq, xq, crit.nnn, D.data(), I.data());
597  double t_search = (getmillisecs() - t0) / 1e3;
598 
599  double perf = crit.evaluate (D.data(), I.data());
600 
601  bool keep = ops->add (perf, t_search, combination_name (cno), cno);
602 
603  if (verbose)
604  printf(" %ld/%ld: %s perf=%.3f t=%.3f s %s\n", cno, n_comb,
605  combination_name (cno).c_str(), perf, t_search,
606  keep ? "*" : "");
607  }
608  return;
609  }
610 
611  int n_exp = n_experiments;
612 
613  if (n_exp > n_comb) n_exp = n_comb;
614  FAISS_THROW_IF_NOT (n_comb == 1 || n_exp > 2);
615  std::vector<int> perm (n_comb);
616  // make sure the slowest and fastest experiment are run
617  perm[0] = 0;
618  if (n_comb > 1) {
619  perm[1] = n_comb - 1;
620  rand_perm (&perm[2], n_comb - 2, 1234);
621  for (int i = 2; i < perm.size(); i++) perm[i] ++;
622  }
623 
624  for (size_t xp = 0; xp < n_exp; xp++) {
625  size_t cno = perm[xp];
626 
627  if (verbose)
628  printf(" %ld/%d: cno=%ld %s ", xp, n_exp, cno,
629  combination_name (cno).c_str());
630 
631  {
632  double lower_bound_t = 0.0;
633  double upper_bound_perf = 1.0;
634  for (int i = 0; i < ops->all_pts.size(); i++) {
635  update_bounds (cno, ops->all_pts[i],
636  &upper_bound_perf, &lower_bound_t);
637  }
638  double best_t = ops->t_for_perf (upper_bound_perf);
639  if (verbose)
640  printf ("bounds [perf<=%.3f t>=%.3f] %s",
641  upper_bound_perf, lower_bound_t,
642  best_t <= lower_bound_t ? "skip\n" : "");
643  if (best_t <= lower_bound_t) continue;
644  }
645 
646  set_index_parameters (index, cno);
647  std::vector<Index::idx_t> I(nq * crit.nnn);
648  std::vector<float> D(nq * crit.nnn);
649 
650  double t0 = getmillisecs ();
651 
652  int nrun = 0;
653  double t_search;
654 
655  do {
656 
657  if (thread_over_batches) {
658 #pragma omp parallel for
659  for (size_t q0 = 0; q0 < nq; q0 += batchsize) {
660  size_t q1 = q0 + batchsize;
661  if (q1 > nq) q1 = nq;
662  index->search (q1 - q0, xq + q0 * index->d,
663  crit.nnn,
664  D.data() + q0 * crit.nnn,
665  I.data() + q0 * crit.nnn);
666  }
667  } else {
668  for (size_t q0 = 0; q0 < nq; q0 += batchsize) {
669  size_t q1 = q0 + batchsize;
670  if (q1 > nq) q1 = nq;
671  index->search (q1 - q0, xq + q0 * index->d,
672  crit.nnn,
673  D.data() + q0 * crit.nnn,
674  I.data() + q0 * crit.nnn);
675  }
676  }
677  nrun ++;
678  t_search = (getmillisecs() - t0) / 1e3;
679 
680  } while (t_search < min_test_duration);
681 
682  t_search /= nrun;
683 
684  double perf = crit.evaluate (D.data(), I.data());
685 
686  bool keep = ops->add (perf, t_search, combination_name (cno), cno);
687 
688  if (verbose)
689  printf(" perf %.3f t %.3f (%d runs) %s\n",
690  perf, t_search, nrun,
691  keep ? "*" : "");
692  }
693 }
694 
695 /***************************************************************
696  * index_factory
697  ***************************************************************/
698 
699 namespace {
700 
701 struct VTChain {
702  std::vector<VectorTransform *> chain;
703  ~VTChain () {
704  for (int i = 0; i < chain.size(); i++) {
705  delete chain[i];
706  }
707  }
708 };
709 
710 
711 /// what kind of training does this coarse quantizer require?
712 char get_trains_alone(const Index *coarse_quantizer) {
713  return
714  dynamic_cast<const MultiIndexQuantizer*>(coarse_quantizer) ? 1 :
715  dynamic_cast<const IndexHNSWFlat*>(coarse_quantizer) ? 2 :
716  0;
717 }
718 
719 
720 }
721 
722 Index *index_factory (int d, const char *description_in, MetricType metric)
723 {
724  VTChain vts;
725  Index *coarse_quantizer = nullptr;
726  Index *index = nullptr;
727  bool add_idmap = false;
728  bool make_IndexRefineFlat = false;
729 
730  ScopeDeleter1<Index> del_coarse_quantizer, del_index;
731 
732  char description[strlen(description_in) + 1];
733  char *ptr;
734  memcpy (description, description_in, strlen(description_in) + 1);
735 
736  int ncentroids = -1;
737 
738  for (char *tok = strtok_r (description, " ,", &ptr);
739  tok;
740  tok = strtok_r (nullptr, " ,", &ptr)) {
741  int d_out, opq_M, nbit, M, M2, pq_m, ncent;
742  std::string stok(tok);
743 
744  // to avoid mem leaks with exceptions:
745  // do all tests before any instanciation
746 
747  VectorTransform *vt_1 = nullptr;
748  Index *coarse_quantizer_1 = nullptr;
749  Index *index_1 = nullptr;
750 
751  // VectorTransforms
752  if (sscanf (tok, "PCA%d", &d_out) == 1) {
753  vt_1 = new PCAMatrix (d, d_out);
754  d = d_out;
755  } else if (sscanf (tok, "PCAR%d", &d_out) == 1) {
756  vt_1 = new PCAMatrix (d, d_out, 0, true);
757  d = d_out;
758  } else if (sscanf (tok, "RR%d", &d_out) == 1) {
759  vt_1 = new RandomRotationMatrix (d, d_out);
760  d = d_out;
761  } else if (sscanf (tok, "PCAW%d", &d_out) == 1) {
762  vt_1 = new PCAMatrix (d, d_out, -0.5, false);
763  d = d_out;
764  } else if (sscanf (tok, "PCAWR%d", &d_out) == 1) {
765  vt_1 = new PCAMatrix (d, d_out, -0.5, true);
766  d = d_out;
767  } else if (sscanf (tok, "OPQ%d_%d", &opq_M, &d_out) == 2) {
768  vt_1 = new OPQMatrix (d, opq_M, d_out);
769  d = d_out;
770  } else if (sscanf (tok, "OPQ%d", &opq_M) == 1) {
771  vt_1 = new OPQMatrix (d, opq_M);
772  } else if (stok == "L2norm") {
773  vt_1 = new NormalizationTransform (d, 2.0);
774 
775  // coarse quantizers
776  } else if (!coarse_quantizer &&
777  sscanf (tok, "IVF%d_HNSW%d", &ncentroids, &M) == 2) {
778  FAISS_THROW_IF_NOT (metric == METRIC_L2);
779  coarse_quantizer_1 = new IndexHNSWFlat (d, M);
780 
781  } else if (!coarse_quantizer &&
782  sscanf (tok, "IVF%d", &ncentroids) == 1) {
783  if (metric == METRIC_L2) {
784  coarse_quantizer_1 = new IndexFlatL2 (d);
785  } else { // if (metric == METRIC_IP)
786  coarse_quantizer_1 = new IndexFlatIP (d);
787  }
788  } else if (!coarse_quantizer && sscanf (tok, "IMI2x%d", &nbit) == 1) {
789  FAISS_THROW_IF_NOT_MSG (metric == METRIC_L2,
790  "MultiIndex not implemented for inner prod search");
791  coarse_quantizer_1 = new MultiIndexQuantizer (d, 2, nbit);
792  ncentroids = 1 << (2 * nbit);
793  } else if (stok == "IDMap") {
794  add_idmap = true;
795 
796  // IVFs
797  } else if (!index && (stok == "Flat" || stok == "FlatDedup")) {
798  if (coarse_quantizer) {
799  // if there was an IVF in front, then it is an IVFFlat
800  IndexIVF *index_ivf = stok == "Flat" ?
801  new IndexIVFFlat (
802  coarse_quantizer, d, ncentroids, metric) :
803  new IndexIVFFlatDedup (
804  coarse_quantizer, d, ncentroids, metric);
805  index_ivf->quantizer_trains_alone =
806  get_trains_alone (coarse_quantizer);
807  index_ivf->cp.spherical = metric == METRIC_INNER_PRODUCT;
808  del_coarse_quantizer.release ();
809  index_ivf->own_fields = true;
810  index_1 = index_ivf;
811  } else {
812  FAISS_THROW_IF_NOT_MSG (stok != "FlatDedup",
813  "dedup supported only for IVFFlat");
814  index_1 = new IndexFlat (d, metric);
815  }
816  } else if (!index && (stok == "SQ8" || stok == "SQ4" ||
817  stok == "SQfp16")) {
819  stok == "SQ8" ? ScalarQuantizer::QT_8bit :
820  stok == "SQ4" ? ScalarQuantizer::QT_4bit :
821  stok == "SQfp16" ? ScalarQuantizer::QT_fp16 :
823  if (coarse_quantizer) {
824  IndexIVFScalarQuantizer *index_ivf =
826  coarse_quantizer, d, ncentroids, qt, metric);
827  index_ivf->quantizer_trains_alone =
828  get_trains_alone (coarse_quantizer);
829  del_coarse_quantizer.release ();
830  index_ivf->own_fields = true;
831  index_1 = index_ivf;
832  } else {
833  index_1 = new IndexScalarQuantizer (d, qt, metric);
834  }
835  } else if (!index && sscanf (tok, "PQ%d+%d", &M, &M2) == 2) {
836  FAISS_THROW_IF_NOT_MSG(coarse_quantizer,
837  "PQ with + works only with an IVF");
838  FAISS_THROW_IF_NOT_MSG(metric == METRIC_L2,
839  "IVFPQR not implemented for inner product search");
840  IndexIVFPQR *index_ivf = new IndexIVFPQR (
841  coarse_quantizer, d, ncentroids, M, 8, M2, 8);
842  index_ivf->quantizer_trains_alone =
843  get_trains_alone (coarse_quantizer);
844  del_coarse_quantizer.release ();
845  index_ivf->own_fields = true;
846  index_1 = index_ivf;
847  } else if (!index && (sscanf (tok, "PQ%d", &M) == 1 ||
848  sscanf (tok, "PQ%dnp", &M) == 1)) {
849  bool do_polysemous_training = stok.find("np") == std::string::npos;
850  if (coarse_quantizer) {
851  IndexIVFPQ *index_ivf = new IndexIVFPQ (
852  coarse_quantizer, d, ncentroids, M, 8);
853  index_ivf->quantizer_trains_alone =
854  get_trains_alone (coarse_quantizer);
855  index_ivf->metric_type = metric;
856  index_ivf->cp.spherical = metric == METRIC_INNER_PRODUCT;
857  del_coarse_quantizer.release ();
858  index_ivf->own_fields = true;
859  index_ivf->do_polysemous_training = do_polysemous_training;
860  index_1 = index_ivf;
861  } else {
862  IndexPQ *index_pq = new IndexPQ (d, M, 8, metric);
863  index_pq->do_polysemous_training = do_polysemous_training;
864  index_1 = index_pq;
865  }
866  } else if (!index &&
867  sscanf (tok, "HNSW%d_%d+PQ%d", &M, &ncent, &pq_m) == 3) {
868  Index * quant = new IndexFlatL2 (d);
869  IndexHNSW2Level * hidx2l = new IndexHNSW2Level (quant, ncent, pq_m, M);
870  Index2Layer * idx2l = dynamic_cast<Index2Layer*>(hidx2l->storage);
871  idx2l->q1.own_fields = true;
872  index_1 = hidx2l;
873  } else if (!index &&
874  sscanf (tok, "HNSW%d_2x%d+PQ%d", &M, &nbit, &pq_m) == 3) {
875  Index * quant = new MultiIndexQuantizer (d, 2, nbit);
876  IndexHNSW2Level * hidx2l =
877  new IndexHNSW2Level (quant, 1 << (2 * nbit), pq_m, M);
878  Index2Layer * idx2l = dynamic_cast<Index2Layer*>(hidx2l->storage);
879  idx2l->q1.own_fields = true;
880  idx2l->q1.quantizer_trains_alone = 1;
881  index_1 = hidx2l;
882  } else if (!index &&
883  sscanf (tok, "HNSW%d_PQ%d", &M, &pq_m) == 2) {
884  index_1 = new IndexHNSWPQ (d, pq_m, M);
885  } else if (!index &&
886  sscanf (tok, "HNSW%d", &M) == 1) {
887  index_1 = new IndexHNSWFlat (d, M);
888  } else if (!index &&
889  sscanf (tok, "HNSW%d_SQ%d", &M, &pq_m) == 2 &&
890  pq_m == 8) {
891  index_1 = new IndexHNSWSQ (d, ScalarQuantizer::QT_8bit, M);
892  } else if (stok == "RFlat") {
893  make_IndexRefineFlat = true;
894  } else {
895  FAISS_THROW_FMT( "could not parse token \"%s\" in %s\n",
896  tok, description_in);
897  }
898 
899  if (index_1 && add_idmap) {
900  IndexIDMap *idmap = new IndexIDMap(index_1);
901  del_index.set (idmap);
902  idmap->own_fields = true;
903  index_1 = idmap;
904  add_idmap = false;
905  }
906 
907  if (vt_1) {
908  vts.chain.push_back (vt_1);
909  }
910 
911  if (coarse_quantizer_1) {
912  coarse_quantizer = coarse_quantizer_1;
913  del_coarse_quantizer.set (coarse_quantizer);
914  }
915 
916  if (index_1) {
917  index = index_1;
918  del_index.set (index);
919  }
920  }
921 
922  FAISS_THROW_IF_NOT_FMT(index, "descrption %s did not generate an index",
923  description_in);
924 
925  // nothing can go wrong now
926  del_index.release ();
927  del_coarse_quantizer.release ();
928 
929  if (add_idmap) {
930  fprintf(stderr, "index_factory: WARNING: "
931  "IDMap option not used\n");
932  }
933 
934  if (vts.chain.size() > 0) {
935  IndexPreTransform *index_pt = new IndexPreTransform (index);
936  index_pt->own_fields = true;
937  // add from back
938  while (vts.chain.size() > 0) {
939  index_pt->prepend_transform (vts.chain.back ());
940  vts.chain.pop_back ();
941  }
942  index = index_pt;
943  }
944 
945  if (make_IndexRefineFlat) {
946  IndexRefineFlat *index_rf = new IndexRefineFlat (index);
947  index_rf->own_fields = true;
948  index = index_rf;
949  }
950 
951  return index;
952 }
953 
954 IndexBinary *index_binary_factory(int d, const char *description)
955 {
956  IndexBinary *index = nullptr;
957 
958  int ncentroids = -1;
959  int M;
960 
961  if (sscanf(description, "BIVF%d_HNSW%d", &ncentroids, &M) == 2) {
962  IndexBinaryIVF *index_ivf = new IndexBinaryIVF(
963  new IndexBinaryHNSW(d, M), d, ncentroids
964  );
965  index_ivf->own_fields = true;
966  index = index_ivf;
967 
968  } else if (sscanf(description, "BIVF%d", &ncentroids) == 1) {
969  IndexBinaryIVF *index_ivf = new IndexBinaryIVF(
970  new IndexBinaryFlat(d), d, ncentroids
971  );
972  index_ivf->own_fields = true;
973  index = index_ivf;
974 
975  } else if (sscanf(description, "BHNSW%d", &M) == 1) {
976  IndexBinaryHNSW *index_hnsw = new IndexBinaryHNSW(d, M);
977  index = index_hnsw;
978 
979  } else if (std::string(description) == "BFlat") {
980  index = new IndexBinaryFlat(d);
981 
982  } else {
983  FAISS_THROW_IF_NOT_FMT(index, "descrption %s did not generate an index",
984  description);
985  }
986 
987  return index;
988 }
989 
990 
991 } // namespace faiss
void explore(Index *index, size_t nq, const float *xq, const AutoTuneCriterion &crit, OperatingPoints *ops) const
Definition: AutoTune.cpp:578
std::vector< ParameterRange > parameter_ranges
all tunable parameters
Definition: AutoTune.h:134
std::string key
key that identifies this op pt
Definition: AutoTune.h:89
Randomly rotate a set of vectors.
long cno
integer identifer
Definition: AutoTune.h:90
bool do_polysemous_training
false = standard PQ
Definition: IndexPQ.h:70
double min_test_duration
Definition: AutoTune.h:153
double evaluate(const float *D, const idx_t *I) const override
Definition: AutoTune.cpp:62
void display(bool only_optimal=true) const
easy-to-read output
Definition: AutoTune.cpp:229
double perf
performance measure (output of a Criterion)
Definition: AutoTune.h:87
double t_for_perf(double perf) const
get time required to obtain a given performance measure
Definition: AutoTune.cpp:182
idx_t nnn
nb of NNs that the query should request
Definition: AutoTune.h:29
double evaluate(const float *D, const idx_t *I) const override
Definition: AutoTune.cpp:86
bool add(double perf, double t, const std::string &key, size_t cno=0)
add a performance measure. Return whether it is an optimal point
Definition: AutoTune.cpp:120
bool do_polysemous_training
reorder PQ centroids after training?
Definition: IndexIVFPQ.h:40
size_t batchsize
maximum number of queries to submit at a time.
Definition: AutoTune.h:145
Level1Quantizer q1
first level quantizer
Definition: IndexIVFPQ.h:207
virtual double evaluate(const float *D, const idx_t *I) const =0
idx_t nq
nb of queries this criterion is evaluated on
Definition: AutoTune.h:28
std::vector< OperatingPoint > optimal_pts
optimal operating points, sorted by perf
Definition: AutoTune.h:98
void set_groundtruth(int gt_nnn, const float *gt_D_in, const idx_t *gt_I_in)
Definition: AutoTune.cpp:44
ParameterRange & add_range(const char *name)
add a new parameter (or return it if it exists)
Definition: AutoTune.cpp:332
idx_t gt_nnn
nb of GT NNs required to evaluate crterion
Definition: AutoTune.h:30
void all_to_gnuplot(const char *fname) const
output to a format easy to digest by gnuplot
Definition: AutoTune.cpp:196
bool own_fields
should the base index be deallocated?
Definition: IndexFlat.h:109
int d
vector dimension
Definition: Index.h:66
size_t code_size
byte per indexed vector
std::vector< OperatingPoint > all_pts
all operating points
Definition: AutoTune.h:95
size_t ranklist_intersection_size(size_t k1, const long *v1, size_t k2, const long *v2_in)
Definition: utils.cpp:1241
ClusteringParameters cp
to override default clustering params
Definition: IndexIVF.h:44
bool verbose
verbosity level
Definition: Index.h:68
double getmillisecs()
ms elapsed since some arbitrary epoch
Definition: utils.cpp:70
std::vector< float > gt_D
Ground-truth distances (size nq * gt_nnn)
Definition: AutoTune.h:32
std::string combination_name(size_t cno) const
get string representation of the combination
Definition: AutoTune.cpp:287
virtual void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const =0
void update_bounds(size_t cno, const OperatingPoint &op, double *upper_bound_perf, double *lower_bound_t) const
Definition: AutoTune.cpp:564
bool own_fields
! the sub-index
virtual void initialize(const Index *index)
initialize with reasonable parameters for the index
Definition: AutoTune.cpp:346
int verbose
verbosity during exploration
Definition: AutoTune.h:139
int merge_with(const OperatingPoints &other, const std::string &prefix="")
add operating points from other to this, with a prefix to the keys
Definition: AutoTune.cpp:167
virtual void set_index_parameter(Index *index, const std::string &name, double val) const
set one of the parameters
Definition: AutoTune.cpp:452
size_t n_combinations() const
nb of combinations, = product of values sizes
Definition: AutoTune.cpp:278
MetricType metric_type
type of metric this index uses for search
Definition: Index.h:74
void set_index_parameters(Index *index, size_t cno) const
set a combination of parameters on an index
Definition: AutoTune.cpp:419
asymmetric product quantizer (default)
Definition: IndexPQ.h:77
void display() const
print a description on stdout
Definition: AutoTune.cpp:546
HE filter (using ht) + PQ combination.
Definition: IndexPQ.h:81
bool combination_ge(size_t c1, size_t c2) const
returns whether combinations c1 &gt;= c2 in the tuple sense
Definition: AutoTune.cpp:302
bool spherical
do we want normalized centroids?
Definition: Clustering.h:28
bool own_fields
whether object owns the quantizer
Definition: IndexIVF.h:42
possible values of a parameter, sorted from least to most expensive/accurate
Definition: AutoTune.h:125
Index * index_factory(int d, const char *description_in, MetricType metric)
Definition: AutoTune.cpp:722
int n_experiments
nb of experiments during optimization (0 = try all combinations)
Definition: AutoTune.h:142
std::vector< idx_t > gt_I
Ground-truth indexes (size nq * gt_nnn)
Definition: AutoTune.h:33
double t
corresponding execution time (ms)
Definition: AutoTune.h:88
MetricType
Some algorithms support both an inner product version and a L2 search version.
Definition: Index.h:45
bool own_fields
! the sub-index
Definition: MetaIndexes.h:27