15 #include "FaissAssert.h"
18 #include "IndexFlat.h"
19 #include "VectorTransform.h"
23 #include "IndexIVFPQ.h"
24 #include "MetaIndexes.h"
25 #include "IndexScalarQuantizer.h"
31 AutoTuneCriterion::AutoTuneCriterion (idx_t nq, idx_t nnn):
32 nq (nq), nnn (nnn), gt_nnn (0)
37 int gt_nnn,
const float *gt_D_in,
const idx_t *gt_I_in)
41 gt_D.resize (nq * gt_nnn);
42 memcpy (
gt_D.data(), gt_D_in,
sizeof (
gt_D[0]) * nq * gt_nnn);
44 gt_I.resize (nq * gt_nnn);
45 memcpy (
gt_I.data(), gt_I_in,
sizeof (
gt_I[0]) * nq * gt_nnn);
50 OneRecallAtRCriterion::OneRecallAtRCriterion (idx_t nq, idx_t R):
54 double OneRecallAtRCriterion::evaluate(
const float* ,
const idx_t* I)
56 FAISS_THROW_IF_NOT_MSG(
58 "ground truth not initialized");
60 for (idx_t q = 0; q <
nq; q++) {
62 const idx_t* I_line = I + q *
nnn;
63 for (
int i = 0; i < R; i++) {
64 if (I_line[i] == gt_nn) {
70 return n_ok / double(nq);
74 IntersectionCriterion::IntersectionCriterion (idx_t nq, idx_t R):
75 AutoTuneCriterion(nq, R), R(R)
78 double IntersectionCriterion::evaluate(
const float* ,
const idx_t* I)
80 FAISS_THROW_IF_NOT_MSG(
82 "ground truth not initialized");
84 #pragma omp parallel for reduction(+: n_ok)
85 for (idx_t q = 0; q <
nq; q++) {
90 return n_ok / double (nq * R);
97 OperatingPoints::OperatingPoints ()
121 if (perf > a.back().perf) {
124 }
else if (perf == a.back().perf) {
125 if (t < a.back ().t) {
133 for (i = 0; i < a.size(); i++) {
134 if (a[i].perf >= perf)
break;
136 assert (i < a.size());
138 if (a[i].perf == perf) {
141 a.insert (a.begin() + i, op);
148 int i = a.size() - 1;
150 if (a[i].t < a[i - 1].t)
151 a.erase (a.begin() + (i - 1));
160 const std::string & prefix)
163 for (
int i = 0; i < other.
all_pts.size(); i++) {
176 const std::vector<OperatingPoint> & a =
optimal_pts;
177 if (perf > a.back().perf)
return 1e50;
178 int i0 = -1, i1 = a.size() - 1;
179 while (i0 + 1 < i1) {
180 int imed = (i0 + i1 + 1) / 2;
181 if (a[imed].perf < perf) i0 = imed;
190 FILE *f = fopen(fname,
"w");
192 fprintf (stderr,
"cannot open %s", fname);
196 for (
int i = 0; i <
all_pts.size(); i++) {
198 fprintf (f,
"%g %g %s\n", op.
perf, op.
t, op.
key.c_str());
203 void OperatingPoints::optimal_to_gnuplot (
const char *fname)
const
205 FILE *f = fopen(fname,
"w");
207 fprintf (stderr,
"cannot open %s", fname);
211 double prev_perf = 0.0;
214 fprintf (f,
"%g %g\n", prev_perf, op.t);
215 fprintf (f,
"%g %g %s\n", op.perf, op.t, op.key.c_str());
223 const std::vector<OperatingPoint> &pts =
225 printf(
"Tested %ld operating points, %ld ones are optimal:\n",
228 for (
int i = 0; i < pts.size(); i++) {
230 const char *star =
"";
239 printf (
"cno=%ld key=%s perf=%.4f t=%.3f %s\n",
249 ParameterSpace::ParameterSpace ():
250 verbose (1), n_experiments (500),
251 batchsize (1<<30), thread_over_batches (false)
260 ParameterSpace::ParameterSpace (Index *index):
261 verbose (1), n_experiments (500),
262 batchsize (1<<30), thread_over_batches (false)
278 char buf[1000], *wp = buf;
282 size_t j = cno % pr.values.size();
283 cno /= pr.values.size();
285 wp, buf + 1000 - wp,
"%s%s=%g", i == 0 ?
"" :
",",
286 pr.name.c_str(), pr.values[j]);
288 return std::string (buf);
296 size_t j1 = c1 % nval;
297 size_t j2 = c2 % nval;
298 if (!(j1 >= j2))
return false;
307 #define DC(classname) \
308 const classname *ix = dynamic_cast<const classname *>(index)
316 for (
int i = 2; i <= pq.
code_size * 8 / 2; i+= 2)
317 pr.values.push_back(i);
338 for (
int i = 0; i <= 6; i++) {
339 pr.values.push_back (1 << i);
341 index = ix->base_index;
349 for (
int i = 0; i < 13; i++) {
350 size_t nprobe = 1 << i;
351 if (nprobe >= ix->nlist)
break;
352 pr.values.push_back (nprobe);
357 init_pq_ParameterRange (ix->pq, pr);
361 init_pq_ParameterRange (ix->pq, pr);
367 for (
int i = 8; i < 20; i++) {
368 pr_max_codes.values.push_back (1 << i);
370 pr_max_codes.values.push_back (1.0 / 0.0);
376 for (
int i = 0; i <= 6; i++) {
377 pr.values.push_back (1 << i);
385 #define DC(classname) classname *ix = dynamic_cast<classname *>(index)
394 size_t j = cno % pr.values.size();
395 cno /= pr.values.size();
396 double val = pr.values [j];
403 Index *index,
const char *description_in)
const
405 char description[strlen(description_in) + 1];
407 memcpy (description, description_in, strlen(description_in) + 1);
409 for (
char *tok = strtok_r (description,
" ,", &ptr);
411 tok = strtok_r (
nullptr,
" ,", &ptr)) {
414 int ret = sscanf (tok,
"%100[^=]=%lf", name, &val);
415 FAISS_THROW_IF_NOT_FMT (
416 ret == 2,
"could not interpret parameters %s", tok);
423 Index * index,
const std::string & name,
double val)
const
426 printf(
" set %s=%g\n", name.c_str(), val);
428 if (name ==
"verbose") {
434 if (name ==
"verbose") {
438 if (name ==
"k_factor_rf") {
439 ix->k_factor = int(val);
442 index = ix->base_index;
447 if (name ==
"verbose") {
451 if (name ==
"nprobe") {
453 ix->nprobe = int(val);
454 }
else if (name ==
"ht") {
456 if (val >= ix->pq.code_size * 8) {
460 ix->polysemous_ht = int(val);
463 if (val >= ix->pq.code_size * 8) {
464 ix->polysemous_ht = 0;
466 ix->polysemous_ht = int(val);
469 }
else if (name ==
"k_factor") {
472 }
else if (name ==
"max_codes") {
474 ix->max_codes = finite(val) ? size_t(val) : 0;
477 "ParameterSpace::set_index_parameter:"
478 "could not set parameter %s",
485 printf (
"ParameterSpace, %ld parameters, %ld combinations:\n",
489 printf (
" %s: ", pr.name.c_str ());
491 for (
int j = 0; j < pr.values.size(); j++) {
492 printf (
"%c %g", sep, pr.values [j]);
502 double *upper_bound_perf,
503 double *lower_bound_t)
const
506 if (op.
t > *lower_bound_t) *lower_bound_t = op.
t;
509 if (op.
perf < *upper_bound_perf) *upper_bound_perf = op.
perf;
516 size_t nq,
const float *xq,
520 FAISS_THROW_IF_NOT_MSG (nq == crit.
nq,
521 "criterion does not have the same nb of queries");
527 for (
size_t cno = 0; cno < n_comb; cno++) {
529 std::vector<Index::idx_t> I(nq * crit.
nnn);
530 std::vector<float> D(nq * crit.
nnn);
533 index->
search (nq, xq, crit.
nnn, D.data(), I.data());
536 double perf = crit.
evaluate (D.data(), I.data());
541 printf(
" %ld/%ld: %s perf=%.3f t=%.3f s %s\n", cno, n_comb,
550 if (n_exp > n_comb) n_exp = n_comb;
551 FAISS_THROW_IF_NOT (n_comb == 1 || n_exp > 2);
552 std::vector<int> perm (n_comb);
556 perm[1] = n_comb - 1;
557 rand_perm (&perm[2], n_comb - 2, 1234);
558 for (
int i = 2; i < perm.size(); i++) perm[i] ++;
561 for (
size_t xp = 0; xp < n_exp; xp++) {
562 size_t cno = perm[xp];
565 printf(
" %ld/%d: cno=%ld %s ", xp, n_exp, cno,
569 double lower_bound_t = 0.0;
570 double upper_bound_perf = 1.0;
571 for (
int i = 0; i < ops->
all_pts.size(); i++) {
573 &upper_bound_perf, &lower_bound_t);
575 double best_t = ops->
t_for_perf (upper_bound_perf);
577 printf (
"bounds [perf<=%.3f t>=%.3f] %s",
578 upper_bound_perf, lower_bound_t,
579 best_t <= lower_bound_t ?
"skip\n" :
"");
580 if (best_t <= lower_bound_t)
continue;
584 std::vector<Index::idx_t> I(nq * crit.
nnn);
585 std::vector<float> D(nq * crit.
nnn);
590 #pragma omp parallel for
591 for (
size_t q0 = 0; q0 < nq; q0 +=
batchsize) {
593 if (q1 > nq) q1 = nq;
594 index->
search (q1 - q0, xq + q0 * index->
d,
596 D.data() + q0 * crit.
nnn,
597 I.data() + q0 * crit.
nnn);
600 for (
size_t q0 = 0; q0 < nq; q0 +=
batchsize) {
602 if (q1 > nq) q1 = nq;
603 index->
search (q1 - q0, xq + q0 * index->
d,
605 D.data() + q0 * crit.
nnn,
606 I.data() + q0 * crit.
nnn);
612 double perf = crit.
evaluate (D.data(), I.data());
617 printf(
" perf %.3f t %.3f %s\n", perf, t_search,
629 std::vector<VectorTransform *> chain;
631 for (
int i = 0; i < chain.size(); i++) {
642 Index *coarse_quantizer =
nullptr;
643 Index *index =
nullptr;
644 bool add_idmap =
false;
645 bool make_IndexRefineFlat =
false;
649 char description[strlen(description_in) + 1];
651 memcpy (description, description_in, strlen(description_in) + 1);
655 for (
char *tok = strtok_r (description,
" ,", &ptr);
657 tok = strtok_r (
nullptr,
" ,", &ptr)) {
658 int d_out, opq_M, nbit, M, M2;
659 std::string stok(tok);
665 Index *coarse_quantizer_1 =
nullptr;
666 Index *index_1 =
nullptr;
669 if (sscanf (tok,
"PCA%d", &d_out) == 1) {
672 }
else if (sscanf (tok,
"PCAR%d", &d_out) == 1) {
673 vt_1 =
new PCAMatrix (d, d_out, 0,
true);
675 }
else if (sscanf (tok,
"PCAW%d", &d_out) == 1) {
676 vt_1 =
new PCAMatrix (d, d_out, -0.5,
false);
678 }
else if (sscanf (tok,
"PCAWR%d", &d_out) == 1) {
679 vt_1 =
new PCAMatrix (d, d_out, -0.5,
true);
681 }
else if (sscanf (tok,
"OPQ%d_%d", &opq_M, &d_out) == 2) {
684 }
else if (sscanf (tok,
"OPQ%d", &opq_M) == 1) {
686 }
else if (stok ==
"L2norm") {
690 }
else if (!coarse_quantizer &&
691 sscanf (tok,
"IVF%d", &ncentroids) == 1) {
692 if (metric == METRIC_L2) {
697 }
else if (!coarse_quantizer && sscanf (tok,
"IMI2x%d", &nbit) == 1) {
698 FAISS_THROW_IF_NOT_MSG (metric == METRIC_L2,
699 "MultiIndex not implemented for inner prod search");
701 ncentroids = 1 << (2 * nbit);
702 }
else if (stok ==
"IDMap") {
706 }
else if (!index && stok ==
"Flat") {
707 if (coarse_quantizer) {
710 coarse_quantizer, d, ncentroids, metric);
714 index_ivf->
cp.
spherical = metric == METRIC_INNER_PRODUCT;
715 del_coarse_quantizer.release ();
721 }
else if (!index && (stok ==
"SQ8" || stok ==
"SQ4")) {
726 if (coarse_quantizer) {
729 coarse_quantizer, d, ncentroids, qt, metric);
733 del_coarse_quantizer.release ();
739 }
else if (!index && sscanf (tok,
"PQ%d+%d", &M, &M2) == 2) {
740 FAISS_THROW_IF_NOT_MSG(coarse_quantizer,
741 "PQ with + works only with an IVF");
742 FAISS_THROW_IF_NOT_MSG(metric == METRIC_L2,
743 "IVFPQR not implemented for inner product search");
745 coarse_quantizer, d, ncentroids, M, 8, M2, 8);
749 del_coarse_quantizer.release ();
752 }
else if (!index && sscanf (tok,
"PQ%d", &M) == 1) {
753 if (coarse_quantizer) {
755 coarse_quantizer, d, ncentroids, M, 8);
760 index_ivf->
cp.
spherical = metric == METRIC_INNER_PRODUCT;
761 del_coarse_quantizer.release ();
770 }
else if (stok ==
"RFlat") {
771 make_IndexRefineFlat =
true;
773 FAISS_THROW_FMT(
"could not parse token \"%s\" in %s\n",
774 tok, description_in);
777 if (index_1 && add_idmap) {
779 del_index.set (idmap);
786 vts.chain.push_back (vt_1);
789 if (coarse_quantizer_1) {
790 coarse_quantizer = coarse_quantizer_1;
791 del_coarse_quantizer.set (coarse_quantizer);
796 del_index.set (index);
800 FAISS_THROW_IF_NOT_FMT(index,
"descrption %s did not generate an index",
804 del_index.release ();
805 del_coarse_quantizer.release ();
808 fprintf(stderr,
"index_factory: WARNING: "
809 "IDMap option not used\n");
812 if (vts.chain.size() > 0) {
816 while (vts.chain.size() > 0) {
817 index_pt->prepend_transform (vts.chain.back());
818 vts.chain.pop_back ();
823 if (make_IndexRefineFlat) {
void explore(Index *index, size_t nq, const float *xq, const AutoTuneCriterion &crit, OperatingPoints *ops) const
std::vector< ParameterRange > parameter_ranges
all tunable parameters
std::string key
key that identifies this op pt
long cno
integer identifer
bool do_polysemous_training
false = standard PQ
void display(bool only_optimal=true) const
easy-to-read output
double perf
performance measure (output of a Criterion)
double t_for_perf(double perf) const
get time required to obtain a given performance measure
idx_t nnn
nb of NNs that the query should request
bool quantizer_trains_alone
just pass over the trainset to quantizer
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
bool do_polysemous_training
reorder PQ centroids after training?
size_t batchsize
maximum number of queries to submit at a time.
virtual double evaluate(const float *D, const idx_t *I) const =0
idx_t nq
nb of queries this criterion is evaluated on
std::vector< OperatingPoint > optimal_pts
optimal operating points, sorted by perf
void set_groundtruth(int gt_nnn, const float *gt_D_in, const idx_t *gt_I_in)
ParameterRange & add_range(const char *name)
add a new parameter
idx_t gt_nnn
nb of GT NNs required to evaluate crterion
void all_to_gnuplot(const char *fname) const
output to a format easy to digest by gnuplot
bool own_fields
should the base index be deallocated?
size_t code_size
byte per indexed vector
ClusteringParameters cp
to override default clustering params
bool own_fields
whether object owns the quantizer
std::vector< OperatingPoint > all_pts
all operating points
size_t ranklist_intersection_size(size_t k1, const long *v1, size_t k2, const long *v2_in)
bool verbose
verbosity level
double getmillisecs()
ms elapsed since some arbitrary epoch
std::vector< float > gt_D
Ground-truth distances (size nq * gt_nnn)
std::string combination_name(size_t cno) const
get string representation of the combination
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
virtual void initialize(const Index *index)
initialize with reasonable parameters for the index
int verbose
verbosity during exploration
int merge_with(const OperatingPoints &other, const std::string &prefix="")
add operating points from other to this, with a prefix to the keys
virtual void set_index_parameter(Index *index, const std::string &name, double val) const
set one of the parameters
size_t n_combinations() const
nb of combinations, = product of values sizes
MetricType metric_type
type of metric this index uses for search
void set_index_parameters(Index *index, size_t cno) const
set a combination of parameters on an index
asymmetric product quantizer (default)
void display() const
print a description on stdout
HE filter (using ht) + PQ combination.
bool combination_ge(size_t c1, size_t c2) const
returns whether combinations c1 >= c2 in the tuple sense
bool spherical
do we want normalized centroids?
possible values of a parameter, sorted from least to most expensive/accurate
Index * index_factory(int d, const char *description_in, MetricType metric)
int n_experiments
nb of experiments during optimization (0 = try all combinations)
std::vector< idx_t > gt_I
Ground-truth indexes (size nq * gt_nnn)
double t
corresponding execution time (ms)
MetricType
Some algorithms support both an inner product vetsion and a L2 search version.
bool own_fields
! the sub-index