13 #include "ProductQuantizer.h"
22 #include "FaissAssert.h"
23 #include "VectorTransform.h"
24 #include "IndexFlat.h"
32 int sgemm_ (
const char *transa,
const char *transb, FINTEGER *m, FINTEGER *
33 n, FINTEGER *k,
const float *alpha,
const float *a,
34 FINTEGER *lda,
const float *b, FINTEGER *
35 ldb,
float *beta,
float *c, FINTEGER *ldc);
46 template <
typename CT,
class C>
47 void pq_estimators_from_tables_Mmul4 (
int M,
const CT * codes,
49 const float * __restrict dis_table,
56 for (
size_t j = 0; j < ncodes; j++) {
58 const float *dt = dis_table;
60 for (
size_t m = 0; m < M; m+=4) {
62 dism = dt[*codes++]; dt += ksub;
63 dism += dt[*codes++]; dt += ksub;
64 dism += dt[*codes++]; dt += ksub;
65 dism += dt[*codes++]; dt += ksub;
69 if (C::cmp (heap_dis[0], dis)) {
70 heap_pop<C> (k, heap_dis, heap_ids);
71 heap_push<C> (k, heap_dis, heap_ids, dis, j);
77 template <
typename CT,
class C>
78 void pq_estimators_from_tables_M4 (
const CT * codes,
80 const float * __restrict dis_table,
87 for (
size_t j = 0; j < ncodes; j++) {
89 const float *dt = dis_table;
90 dis = dt[*codes++]; dt += ksub;
91 dis += dt[*codes++]; dt += ksub;
92 dis += dt[*codes++]; dt += ksub;
95 if (C::cmp (heap_dis[0], dis)) {
96 heap_pop<C> (k, heap_dis, heap_ids);
97 heap_push<C> (k, heap_dis, heap_ids, dis, j);
103 template <
typename CT,
class C>
104 static inline void pq_estimators_from_tables (
const ProductQuantizer * pq,
107 const float * dis_table,
115 pq_estimators_from_tables_M4<CT, C> (codes, ncodes,
116 dis_table, pq->ksub, k,
121 if (pq->M % 4 == 0) {
122 pq_estimators_from_tables_Mmul4<CT, C> (pq->M, codes, ncodes,
123 dis_table, pq->ksub, k,
129 const size_t M = pq->M;
130 const size_t ksub = pq->ksub;
131 for (
size_t j = 0; j < ncodes; j++) {
133 const float * __restrict dt = dis_table;
134 for (
int m = 0; m < M; m++) {
138 if (C::cmp (heap_dis[0], dis)) {
139 heap_pop<C> (k, heap_dis, heap_ids);
140 heap_push<C> (k, heap_dis, heap_ids, dis, j);
152 ProductQuantizer::ProductQuantizer (
size_t d,
size_t M,
size_t nbits):
153 d(d), M(M), nbits(nbits), assign_index(nullptr)
155 set_derived_values ();
158 ProductQuantizer::ProductQuantizer ():
159 d(0), M(1), nbits(0), assign_index(nullptr)
161 set_derived_values ();
168 FAISS_THROW_IF_NOT (
d % M == 0);
175 train_type = Train_default;
182 ksub *
dsub *
sizeof (centroids_[0]));
186 static void init_hypercube (
int d,
int nbits,
187 int n,
const float * x,
191 std::vector<float> mean (d);
192 for (
int i = 0; i < n; i++)
193 for (
int j = 0; j < d; j++)
194 mean [j] += x[i * d + j];
197 for (
int j = 0; j < d; j++) {
199 if (fabs(mean[j]) > maxm) maxm = fabs(mean[j]);
202 for (
int i = 0; i < (1 << nbits); i++) {
203 float * cent = centroids + i * d;
204 for (
int j = 0; j < nbits; j++)
205 cent[j] = mean [j] + (((i >> j) & 1) ? 1 : -1) * maxm;
206 for (
int j = nbits; j < d; j++)
213 static void init_hypercube_pca (
int d,
int nbits,
214 int n,
const float * x,
217 PCAMatrix pca (d, nbits);
221 for (
int i = 0; i < (1 << nbits); i++) {
222 float * cent = centroids + i * d;
223 for (
int j = 0; j < d; j++) {
224 cent[j] = pca.mean[j];
226 for (
int k = 0; k < nbits; k++)
228 sqrt (pca.eigenvalues [k]) *
229 (((i >> k) & 1) ? 1 : -1) *
230 pca.PCAMat [j + k * d];
236 void ProductQuantizer::train (
int n,
const float * x)
240 final_train_type = train_type;
244 final_train_type = Train_default;
245 printf (
"cannot train hypercube: nbits=%ld > log2(d=%ld)\n",
250 float * xslice =
new float[n *
dsub];
251 ScopeDeleter<float> del (xslice);
252 for (
int m = 0; m <
M; m++) {
253 for (
int j = 0; j < n; j++)
254 memcpy (xslice + j *
dsub,
255 x + j * d + m * dsub,
256 dsub *
sizeof(
float));
258 Clustering clus (dsub, ksub,
cp);
261 if (final_train_type != Train_default) {
262 clus.centroids.resize (dsub * ksub);
265 switch (final_train_type) {
267 init_hypercube (dsub, nbits, n, xslice,
268 clus.centroids.data ());
271 init_hypercube_pca (dsub, nbits, n, xslice,
272 clus.centroids.data ());
275 memcpy (clus.centroids.data(),
277 dsub * ksub *
sizeof (float));
284 printf (
"Training PQ slice %d/%zd\n", m, M);
286 IndexFlatL2 index (dsub);
294 Clustering clus (dsub, ksub,
cp);
298 printf (
"Training all PQ slices at once\n");
301 IndexFlatL2 index (dsub);
304 for (
int m = 0; m <
M; m++) {
314 float distances [
ksub];
315 for (
size_t m = 0; m <
M; m++) {
318 const float * xsub = x + m *
dsub;
320 fvec_L2sqr_ny (distances, xsub,
get_centroids(m, 0), dsub, ksub);
324 for (i = 0; i <
ksub; i++) {
325 float dis = distances [i];
332 case 1: code[m] = (uint8_t) idxm;
break;
333 case 2: ((uint16_t *) code)[m] = (uint16_t) idxm;
break;
342 for (
size_t m = 0; m <
M; m++) {
344 sizeof(
float) * dsub);
347 const uint16_t *c = (
const uint16_t*) code;
348 for (
size_t m = 0; m <
M; m++) {
350 sizeof(
float) * dsub);
358 for (
size_t i = 0; i < n; i++) {
367 for (
size_t m = 0; m <
M; m++) {
372 for (
size_t j = 0; j <
ksub; j++) {
380 case 1: code[m] = (uint8_t) idxm;
break;
381 case 2: ((uint16_t *) code)[m] = (uint16_t) idxm;
break;
392 #pragma omp parallel for
393 for (
size_t i = 0; i < n; i++)
397 float *dis_tables =
new float [n * ksub *
M];
401 #pragma omp parallel for
402 for (
size_t i = 0; i < n; i++) {
404 const float * tab = dis_tables + i * ksub *
M;
412 float * dis_table)
const
416 for (m = 0; m <
M; m++) {
417 fvec_L2sqr_ny (dis_table + m * ksub,
425 void ProductQuantizer::compute_inner_prod_table (
const float * x,
426 float * dis_table)
const
430 for (m = 0; m <
M; m++) {
431 fvec_inner_products_ny (dis_table + m * ksub,
443 float * dis_tables)
const
448 #pragma omp parallel for
449 for (
size_t i = 0; i < nx; i++) {
455 for (
int m = 0; m <
M; m++) {
458 ksub, centroids.data() + m * dsub *
ksub,
459 dis_tables + ksub * m,
465 void ProductQuantizer::compute_inner_prod_tables (
468 float * dis_tables)
const
473 #pragma omp parallel for
474 for (
size_t i = 0; i < nx; i++) {
475 compute_inner_prod_table (x + i * d, dis_tables + i * ksub * M);
481 for (
int m = 0; m <
M; m++) {
482 FINTEGER ldc = ksub *
M, nxi = nx, ksubi =
ksub,
483 dsubi =
dsub, di =
d;
484 float one = 1.0, zero = 0;
486 sgemm_ (
"Transposed",
"Not transposed",
487 &ksubi, &nxi, &dsubi,
488 &one, ¢roids [m * dsub * ksub], &dsubi,
490 &zero, dis_tables + ksub * m, &ldc);
496 template <
typename CT,
class C>
497 static void pq_knn_search_with_tables (
498 const ProductQuantizer * pq,
499 const float *dis_tables,
500 const uint8_t * codes,
503 bool init_finalize_heap)
505 size_t k = res->k, nx = res->nh;
506 size_t ksub = pq->ksub, M = pq->M;
509 #pragma omp parallel for
510 for (
size_t i = 0; i < nx; i++) {
512 const float* dis_table = dis_tables + i * ksub * M;
515 long * __restrict heap_ids = res->ids + i * k;
516 float * __restrict heap_dis = res->val + i * k;
518 if (init_finalize_heap) {
519 heap_heapify<C> (k, heap_dis, heap_ids);
522 pq_estimators_from_tables<CT, C> (pq,
525 k, heap_dis, heap_ids);
526 if (init_finalize_heap) {
527 heap_reorder<C> (k, heap_dis, heap_ids);
543 const uint8_t * codes,
546 bool init_finalize_heap)
const
548 FAISS_THROW_IF_NOT (nx == res->
nh);
549 float * dis_tables =
new float [nx * ksub *
M];
555 pq_knn_search_with_tables<uint8_t, CMax<float, long> > (
556 this, dis_tables, codes, ncodes, res, init_finalize_heap);
559 pq_knn_search_with_tables<uint16_t, CMax<float, long> > (
560 this, dis_tables, codes, ncodes, res, init_finalize_heap);
568 const uint8_t * codes,
571 bool init_finalize_heap)
const
573 FAISS_THROW_IF_NOT (nx == res->
nh);
574 float * dis_tables =
new float [nx * ksub *
M];
576 compute_inner_prod_tables (nx, x, dis_tables);
580 pq_knn_search_with_tables<uint8_t, CMin<float, long> > (
581 this, dis_tables, codes, ncodes, res, init_finalize_heap);
584 pq_knn_search_with_tables<uint16_t, CMin<float, long> > (
585 this, dis_tables, codes, ncodes, res, init_finalize_heap);
592 static float sqr (
float x) {
596 void ProductQuantizer::compute_sdc_table ()
600 for (
int m = 0; m <
M; m++) {
602 const float *cents = centroids.data() + m * ksub *
dsub;
606 for (
int i = 0; i <
ksub; i++) {
607 const float *centi = cents + i *
dsub;
608 for (
int j = 0; j <
ksub; j++) {
610 const float *centj = cents + j *
dsub;
611 for (
int k = 0; k <
dsub; k++)
612 accu += sqr (centi[k] - centj[k]);
613 dis_tab [i + j *
ksub] = accu;
619 void ProductQuantizer::search_sdc (
const uint8_t * qcodes,
621 const uint8_t * bcodes,
623 float_maxheap_array_t * res,
624 bool init_finalize_heap)
const
631 #pragma omp parallel for
632 for (
size_t i = 0; i < nq; i++) {
635 long * heap_ids = res->ids + i * k;
636 float * heap_dis = res->val + i * k;
637 const uint8_t * qcode = qcodes + i *
code_size;
639 if (init_finalize_heap)
640 maxheap_heapify (k, heap_dis, heap_ids);
642 const uint8_t * bcode = bcodes;
643 for (
size_t j = 0; j < nb; j++) {
646 for (
int m = 0; m <
M; m++) {
647 dis += tab[bcode[m] + qcode[m] *
ksub];
650 if (dis < heap_dis[0]) {
651 maxheap_pop (k, heap_dis, heap_ids);
652 maxheap_push (k, heap_dis, heap_ids, dis, j);
657 if (init_finalize_heap)
658 maxheap_reorder (k, heap_dis, heap_ids);
void set_params(const float *centroids, int m)
Define the centroids for subquantizer m.
intialize centroids with nbits-D hypercube
size_t nbits
number of bits per quantization index
void decode(const uint8_t *code, float *x) const
decode a vector from a given code (or n vectors if third argument)
size_t byte_per_idx
nb bytes per code component (1 or 2)
intialize centroids with nbits-D hypercube
void set_derived_values()
compute derived values when d, M and nbits have been set
std::vector< float > sdc_table
Symmetric Distance Table.
share dictionary accross PQ segments
size_t dsub
dimensionality of each subvector
void compute_distance_tables(size_t nx, const float *x, float *dis_tables) const
void compute_code_from_distance_table(const float *tab, uint8_t *code) const
void compute_codes(const float *x, uint8_t *codes, size_t n) const
same as compute_code for several vectors
void compute_distance_table(const float *x, float *dis_table) const
void search(const float *x, size_t nx, const uint8_t *codes, const size_t ncodes, float_maxheap_array_t *res, bool init_finalize_heap=true) const
size_t code_size
byte per indexed vector
size_t ksub
number of centroids for each subquantizer
void search_ip(const float *x, size_t nx, const uint8_t *codes, const size_t ncodes, float_minheap_array_t *res, bool init_finalize_heap=true) const
void pairwise_L2sqr(long d, long nq, const float *xq, long nb, const float *xb, float *dis, long ldq, long ldb, long ldd)
void compute_code(const float *x, uint8_t *code) const
Quantize one vector with the product quantizer.
the centroids are already initialized
ClusteringParameters cp
parameters used during clustering
size_t M
number of subquantizers
float * get_centroids(size_t m, size_t i)
return the centroids associated with subvector m
size_t d
size of the input vectors
bool verbose
verbose during training?
std::vector< float > centroids
Centroid table, size M * ksub * dsub.
train_type_t
initialization