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