Faiss

- Main Page
- Namespaces
- Classes
- Files

- File List

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
44 void AutoTuneCriterion::set_groundtruth (
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++) {
94 n_ok += ranklist_intersection_size (
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
110 void OperatingPoints::clear ()
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
167 int OperatingPoints::merge_with (const OperatingPoints &other,
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
278 size_t ParameterSpace::n_combinations () const
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
332 ParameterRange &ParameterSpace::add_range(const char * name)
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
432 void ParameterSpace::set_index_parameters (
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
452 void ParameterSpace::set_index_parameter (
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
546 void ParameterSpace::display () const
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
578 void ParameterSpace::explore (Index *index,
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")) {
818 ScalarQuantizer::QuantizerType qt =
819 stok == "SQ8" ? ScalarQuantizer::QT_8bit :
820 stok == "SQ4" ? ScalarQuantizer::QT_4bit :
821 stok == "SQfp16" ? ScalarQuantizer::QT_fp16 :
822 ScalarQuantizer::QT_4bit;
823 if (coarse_quantizer) {
824 IndexIVFScalarQuantizer *index_ivf =
825 new IndexIVFScalarQuantizer (
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
faiss::ParameterSpace::explore
void explore(Index *index, size_t nq, const float *xq, const AutoTuneCriterion &crit, OperatingPoints *ops) const
Definition: AutoTune.cpp:578
faiss::IndexIVF
Definition: IndexIVF.h:91
faiss::ParameterSpace::parameter_ranges
std::vector< ParameterRange > parameter_ranges
all tunable parameters
Definition: AutoTune.h:134
faiss::OperatingPoint::key
std::string key
key that identifies this op pt
Definition: AutoTune.h:89
faiss::IndexHNSWSQ
Definition: IndexHNSW.h:159
faiss::ScalarQuantizer::QuantizerType
QuantizerType
Definition: IndexScalarQuantizer.h:34
faiss::IndexFlat
Definition: IndexFlat.h:22
faiss::RandomRotationMatrix
Randomly rotate a set of vectors.
Definition: VectorTransform.h:116
faiss::OperatingPoint::cno
long cno
integer identifer
Definition: AutoTune.h:90
faiss::IndexIVFPQR
Definition: IndexIVFPQ.h:156
faiss::IndexPQ::do_polysemous_training
bool do_polysemous_training
false = standard PQ
Definition: IndexPQ.h:70
faiss::OperatingPoints::clear
void clear()
Definition: AutoTune.cpp:110
faiss::ParameterSpace::min_test_duration
double min_test_duration
Definition: AutoTune.h:153
faiss::OneRecallAtRCriterion::evaluate
double evaluate(const float *D, const idx_t *I) const override
Definition: AutoTune.cpp:62
faiss::OperatingPoint
Definition: AutoTune.h:86
faiss::IndexScalarQuantizer
Definition: IndexScalarQuantizer.h:102
faiss::OperatingPoints::display
void display(bool only_optimal=true) const
easy-to-read output
Definition: AutoTune.cpp:229
faiss::IndexPQ
Definition: IndexPQ.h:27
faiss::OperatingPoint::perf
double perf
performance measure (output of a Criterion)
Definition: AutoTune.h:87
faiss::ScalarQuantizer::QT_4bit
4 bits per component
Definition: IndexScalarQuantizer.h:36
faiss::OperatingPoints
Definition: AutoTune.h:93
faiss::OperatingPoints::t_for_perf
double t_for_perf(double perf) const
get time required to obtain a given performance measure
Definition: AutoTune.cpp:182
faiss::AutoTuneCriterion::nnn
idx_t nnn
nb of NNs that the query should request
Definition: AutoTune.h:29
faiss::PCAMatrix
Definition: VectorTransform.h:134
faiss::IndexFlatL2
Definition: IndexFlat.h:78
faiss::IntersectionCriterion::evaluate
double evaluate(const float *D, const idx_t *I) const override
Definition: AutoTune.cpp:86
faiss::OperatingPoints::add
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
faiss::OPQMatrix
Definition: VectorTransform.h:189
faiss::ScopeDeleter1
Definition: FaissException.h:52
faiss::IndexIVFPQ::do_polysemous_training
bool do_polysemous_training
reorder PQ centroids after training?
Definition: IndexIVFPQ.h:40
faiss::ParameterSpace::batchsize
size_t batchsize
maximum number of queries to submit at a time.
Definition: AutoTune.h:145
faiss::Index2Layer::q1
Level1Quantizer q1
first level quantizer
Definition: IndexIVFPQ.h:207
faiss::AutoTuneCriterion::evaluate
virtual double evaluate(const float *D, const idx_t *I) const =0
faiss::IndexPreTransform
Definition: VectorTransform.h:253
faiss::IndexIDMap
Definition: MetaIndexes.h:25
faiss::AutoTuneCriterion::nq
idx_t nq
nb of queries this criterion is evaluated on
Definition: AutoTune.h:28
faiss::IndexIVFFlat
Definition: IndexIVFFlat.h:25
faiss::OperatingPoints::optimal_pts
std::vector< OperatingPoint > optimal_pts
optimal operating points, sorted by perf
Definition: AutoTune.h:98
faiss::IndexFlatIP
Definition: IndexFlat.h:72
faiss::AutoTuneCriterion::set_groundtruth
void set_groundtruth(int gt_nnn, const float *gt_D_in, const idx_t *gt_I_in)
Definition: AutoTune.cpp:44
faiss::ParameterSpace::add_range
ParameterRange & add_range(const char *name)
add a new parameter (or return it if it exists)
Definition: AutoTune.cpp:332
faiss::IndexRefineFlat
Definition: IndexFlat.h:102
faiss::Index2Layer
Definition: IndexIVFPQ.h:205
faiss::AutoTuneCriterion::gt_nnn
idx_t gt_nnn
nb of GT NNs required to evaluate crterion
Definition: AutoTune.h:30
faiss::OperatingPoints::all_to_gnuplot
void all_to_gnuplot(const char *fname) const
output to a format easy to digest by gnuplot
Definition: AutoTune.cpp:196
faiss::IndexRefineFlat::own_fields
bool own_fields
should the base index be deallocated?
Definition: IndexFlat.h:109
faiss::IndexHNSW2Level
Definition: IndexHNSW.h:168
faiss::Index::d
int d
vector dimension
Definition: Index.h:66
faiss::ProductQuantizer::code_size
size_t code_size
byte per indexed vector
Definition: ProductQuantizer.h:33
faiss::IndexIVFPQ
Definition: IndexIVFPQ.h:35
faiss::Level1Quantizer::quantizer_trains_alone
char quantizer_trains_alone
Definition: IndexIVF.h:41
faiss::ParameterSpace::thread_over_batches
bool thread_over_batches
Definition: AutoTune.h:149
faiss::OperatingPoints::all_pts
std::vector< OperatingPoint > all_pts
all operating points
Definition: AutoTune.h:95
faiss::ranklist_intersection_size
size_t ranklist_intersection_size(size_t k1, const long *v1, size_t k2, const long *v2_in)
Definition: utils.cpp:1241
faiss::Level1Quantizer::cp
ClusteringParameters cp
to override default clustering params
Definition: IndexIVF.h:44
faiss::AutoTuneCriterion
Definition: AutoTune.h:26
faiss::Index::verbose
bool verbose
verbosity level
Definition: Index.h:68
faiss::getmillisecs
double getmillisecs()
ms elapsed since some arbitrary epoch
Definition: utils.cpp:70
faiss::AutoTuneCriterion::gt_D
std::vector< float > gt_D
Ground-truth distances (size nq * gt_nnn)
Definition: AutoTune.h:32
faiss::IndexIVFScalarQuantizer
Definition: IndexScalarQuantizer.h:149
faiss::ParameterSpace::combination_name
std::string combination_name(size_t cno) const
get string representation of the combination
Definition: AutoTune.cpp:287
faiss::Index::search
virtual void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const =0
faiss::MultiIndexQuantizer
Definition: IndexPQ.h:136
faiss::ParameterSpace::update_bounds
void update_bounds(size_t cno, const OperatingPoint &op, double *upper_bound_perf, double *lower_bound_t) const
Definition: AutoTune.cpp:564
faiss::VectorTransform
Definition: VectorTransform.h:27
faiss::IndexPreTransform::own_fields
bool own_fields
! the sub-index
Definition: VectorTransform.h:258
faiss::ParameterSpace::initialize
virtual void initialize(const Index *index)
initialize with reasonable parameters for the index
Definition: AutoTune.cpp:346
faiss::ParameterSpace::verbose
int verbose
verbosity during exploration
Definition: AutoTune.h:139
faiss::IndexHNSWPQ
Definition: IndexHNSW.h:148
faiss::OperatingPoints::merge_with
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
faiss::IndexHNSW
Definition: IndexHNSW.h:70
faiss::ParameterSpace::set_index_parameter
virtual void set_index_parameter(Index *index, const std::string &name, double val) const
set one of the parameters
Definition: AutoTune.cpp:452
faiss::ParameterSpace::n_combinations
size_t n_combinations() const
nb of combinations, = product of values sizes
Definition: AutoTune.cpp:278
faiss::Index::metric_type
MetricType metric_type
type of metric this index uses for search
Definition: Index.h:74
faiss::ParameterSpace::set_index_parameters
void set_index_parameters(Index *index, size_t cno) const
set a combination of parameters on an index
Definition: AutoTune.cpp:419
faiss::IndexHNSWFlat
Definition: IndexHNSW.h:138
faiss::ScalarQuantizer::QT_8bit
8 bits per component
Definition: IndexScalarQuantizer.h:35
faiss::Index
Definition: Index.h:62
faiss::IndexPQ::ST_PQ
asymmetric product quantizer (default)
Definition: IndexPQ.h:77
faiss::ParameterSpace::display
void display() const
print a description on stdout
Definition: AutoTune.cpp:546
faiss::IndexPQ::ST_polysemous
HE filter (using ht) + PQ combination.
Definition: IndexPQ.h:81
faiss::IndexShards
Definition: MetaIndexes.h:85
faiss::ProductQuantizer
Definition: ProductQuantizer.h:24
faiss::NormalizationTransform
Definition: VectorTransform.h:237
faiss::ParameterSpace::combination_ge
bool combination_ge(size_t c1, size_t c2) const
returns whether combinations c1 &gt;= c2 in the tuple sense
Definition: AutoTune.cpp:302
faiss::ClusteringParameters::spherical
bool spherical
do we want normalized centroids?
Definition: Clustering.h:28
faiss::Level1Quantizer::own_fields
bool own_fields
whether object owns the quantizer
Definition: IndexIVF.h:42
faiss::ParameterRange
possible values of a parameter, sorted from least to most expensive/accurate
Definition: AutoTune.h:125
faiss::index_factory
Index * index_factory(int d, const char *description_in, MetricType metric)
Definition: AutoTune.cpp:722
faiss::ParameterSpace::n_experiments
int n_experiments
nb of experiments during optimization (0 = try all combinations)
Definition: AutoTune.h:142
faiss::AutoTuneCriterion::gt_I
std::vector< idx_t > gt_I
Ground-truth indexes (size nq * gt_nnn)
Definition: AutoTune.h:33
faiss::OperatingPoint::t
double t
corresponding execution time (ms)
Definition: AutoTune.h:88
faiss::MetricType
MetricType
Some algorithms support both an inner product version and a L2 search version.
Definition: Index.h:45
faiss::IndexIVFFlatDedup
Definition: IndexIVFFlat.h:77
faiss::IndexIDMap::own_fields
bool own_fields
! the sub-index
Definition: MetaIndexes.h:27