23 #include "FaissAssert.h"
33 IndexPQ::IndexPQ (
int d,
size_t M,
size_t nbits,
MetricType metric):
34 Index(d, metric), pq(d, M, nbits)
56 void IndexPQ::set_typename ()
60 index_typename = s.str();
71 if (ntrain_perm > n / 4)
74 printf (
"PQ training on %ld points, remains %ld points: "
75 "training polysemous on %s\n",
76 n - ntrain_perm, ntrain_perm,
77 ntrain_perm == 0 ?
"centroids" :
"these");
79 pq.train(n - ntrain_perm, x);
82 pq, ntrain_perm, x + (n - ntrain_perm) *
d);
106 FAISS_ASSERT (ni == 0 || (i0 >= 0 && i0 + ni <=
ntotal));
107 for (
idx_t i = 0; i < ni; i++) {
116 FAISS_ASSERT (key >= 0 && key <
ntotal);
135 float *distances,
idx_t *labels)
const
138 if (search_type ==
ST_PQ) {
142 size_t(n), size_t(k), labels, distances };
146 size_t(n), size_t(k), labels, distances };
149 indexPQ_stats.nq += n;
150 indexPQ_stats.ncode += n *
ntotal;
157 search_core_polysemous (n, x, k, distances, labels);
161 uint8_t * q_codes =
new uint8_t [n *
pq.
code_size];
172 for (
size_t i = 0; i < n; i++) {
173 const float *xi = x + i *
d;
175 for (
int j = 0; j <
d; j++)
176 if (xi[j] > 0) code [j>>3] |= 1 << (j & 7);
180 if (search_type ==
ST_SDC) {
183 size_t(n), size_t(k), labels, distances};
185 pq.search_sdc (q_codes, n,
codes.data(),
ntotal, &res,
true);
188 int * idistances =
new int [n * k];
191 size_t (n), size_t (k), labels, idistances};
193 if (search_type ==
ST_HE) {
205 for (
int i = 0; i < k * n; i++)
206 distances[i] = idistances[i];
207 delete [] idistances;
211 indexPQ_stats.nq += n;
212 indexPQ_stats.ncode += n *
ntotal;
220 void IndexPQStats::reset()
222 nq = ncode = n_hamming_pass = 0;
225 IndexPQStats indexPQ_stats;
228 template <
class HammingComputer>
229 static size_t polysemous_inner_loop (
230 const IndexPQ & index,
231 const float *dis_table_qi,
const uint8_t *q_code,
232 size_t k,
float *heap_dis,
long *heap_ids)
236 int code_size = index.pq.code_size;
237 int ksub = index.pq.ksub;
238 size_t ntotal = index.ntotal;
239 int ht = index.polysemous_ht;
241 const uint8_t *b_code = index.codes.data();
245 HammingComputer hc (q_code, code_size);
247 for (
long bi = 0; bi < ntotal; bi++) {
248 int hd = hc.hamming (b_code);
254 const float * dis_table = dis_table_qi;
255 for (
int m = 0; m < M; m++) {
256 dis += dis_table [b_code[m]];
260 if (dis < heap_dis[0]) {
261 maxheap_pop (k, heap_dis, heap_ids);
262 maxheap_push (k, heap_dis, heap_ids, dis, bi);
271 void IndexPQ::search_core_polysemous (idx_t n,
const float *x, idx_t k,
272 float *distances, idx_t *labels)
const
278 float * dis_tables =
new float [n *
pq.
ksub *
pq.
M];
282 uint8_t * q_codes =
new uint8_t [n *
pq.
code_size];
287 #pragma omp parallel for
288 for (
idx_t qi = 0; qi < n; qi++) {
297 #pragma omp parallel for reduction (+: n_pass)
298 for (
idx_t qi = 0; qi < n; qi++) {
299 const uint8_t * q_code = q_codes + qi *
pq.
code_size;
301 const float * dis_table_qi = dis_tables + qi *
pq.
M *
pq.
ksub;
303 long * heap_ids = labels + qi * k;
304 float *heap_dis = distances + qi * k;
305 maxheap_heapify (k, heap_dis, heap_ids);
311 n_pass += polysemous_inner_loop<HammingComputer4>
312 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
315 n_pass += polysemous_inner_loop<HammingComputer8>
316 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
319 n_pass += polysemous_inner_loop<HammingComputer16>
320 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
323 n_pass += polysemous_inner_loop<HammingComputer32>
324 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
327 n_pass += polysemous_inner_loop<HammingComputer20>
328 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
332 n_pass += polysemous_inner_loop<HammingComputerM8>
333 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
335 n_pass += polysemous_inner_loop<HammingComputerM4>
336 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
342 n_pass += polysemous_inner_loop<GenHammingComputer8>
343 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
346 n_pass += polysemous_inner_loop<GenHammingComputer16>
347 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
350 n_pass += polysemous_inner_loop<GenHammingComputer32>
351 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
354 n_pass += polysemous_inner_loop<GenHammingComputerM8>
355 (*
this, dis_table_qi, q_code, k, heap_dis, heap_ids);
359 maxheap_reorder (k, heap_dis, heap_ids);
362 indexPQ_stats.nq += n;
363 indexPQ_stats.ncode += n *
ntotal;
364 indexPQ_stats.n_hamming_pass += n_pass;
367 delete [] dis_tables;
384 uint8_t * q_codes =
new uint8_t [n *
pq.
code_size];
395 idx_t nb,
const float *xb,
403 uint8_t * q_codes =
new uint8_t [n *
pq.
code_size];
413 b_codes =
codes.data();
416 memset (hist, 0,
sizeof(*hist) * (nbits + 1));
421 std::vector<long> histi (nbits + 1);
422 hamdis_t *distances =
new hamdis_t [nb * bs];
424 for (
size_t q0 = 0; q0 < n; q0 += bs) {
433 for (
size_t i = 0; i < nb * (q1 - q0); i++)
434 histi [distances [i]]++;
438 for (
int i = 0; i <= nbits; i++)
475 template <
typename T>
478 bool operator() (
size_t i,
size_t j) {
487 template <
typename T>
491 std::vector<int> perm;
498 void init (
const T*x) {
500 for (
int n = 0; n < N; n++)
503 std::sort (perm.begin(), perm.end(), cmp);
513 return x[perm[n]] - x[perm[n - 1]];
517 int get_ord (
int n) {
529 const typename C::T * vals,
typename C::TI * perm) {
531 for (
int i = 1; i < k; i++) {
532 indirect_heap_push<C> (i + 1, vals, perm, perm[i]);
536 for (
int i = k; i < n; i++) {
537 typename C::TI
id = perm[i];
538 typename C::TI top = perm[0];
540 if (C::cmp(vals[top], vals[
id])) {
541 indirect_heap_pop<C> (k, vals, perm);
542 indirect_heap_push<C> (k, vals, perm, id);
550 for (
int i = k - 1; i > 0; i--) {
551 typename C::TI top = perm[0];
552 indirect_heap_pop<C> (i + 1, vals, perm);
558 template <
typename T>
565 std::vector<int> perm;
569 int initial_k, k_factor;
579 void init (
const T*x) {
581 for (
int n = 0; n < N; n++)
590 partial_sort<HC> (next_k - k, N - k, x, &perm[k]);
594 std::sort (perm.begin() + k, perm.end(), cmp);
608 int next_k = (k + 1) * k_factor - 1;
611 return x[perm[n]] - x[perm[n - 1]];
615 int get_ord (
int n) {
649 template <
typename T,
class SSA,
bool use_seen>
660 size_t heap_capacity, heap_size;
664 std::vector <SSA> ssx;
665 std::vector <long> weights;
671 std::vector <uint8_t> seen;
673 MinSumK (
int K,
int M,
int N): K(K), M(M), N(N) {
674 heap_capacity = K *
M;
676 bh_val =
new T[heap_capacity];
677 bh_ids =
new long[heap_capacity];
679 weights.push_back (1);
680 for (
int m = 1; m <
M; m++)
681 weights.push_back(weights[m - 1] * N);
684 long n_ids = weights.back() *
N;
685 seen.resize ((n_ids + 7) / 8);
688 for (
int m = 0; m <
M; m++)
689 ssx.push_back (SSA(N));
693 bool is_seen (
long i) {
694 return (seen[i >> 3] >> (i & 7)) & 1;
697 void mark_seen (
long i) {
699 seen [i >> 3] |= 1 << (i & 7);
702 void run (
const T *x, T * sums,
long * terms) {
705 for (
int m = 0; m <
M; m++)
706 ssx[m].init(x + N * m);
712 for (
int m = 0; m <
M; m++) {
713 sum += ssx[m].get_0();
716 for (
int m = 0; m <
M; m++) {
717 heap_push<HC> (++heap_size, bh_val, bh_ids,
718 sum + ssx[m].get_diff(1),
723 for (
int k = 1; k <
K; k++) {
726 while (is_seen (bh_ids[0])) {
727 assert (heap_size > 0);
728 heap_pop<HC> (heap_size--, bh_val, bh_ids);
731 assert (heap_size > 0);
733 T sum = sums[k] = bh_val[0];
734 long ti = terms[k] = bh_ids[0];
738 heap_pop<HC> (heap_size--, bh_val, bh_ids);
741 heap_pop<HC> (heap_size--, bh_val, bh_ids);
742 }
while (heap_size > 0 && bh_ids[0] == ti);
747 for (
int m = 0; m <
M; m++) {
750 if (n + 1 >= N)
continue;
752 enqueue_follower (ti, m, n, sum);
763 for (
int k = 0; k <
K; k++) {
770 for (
int m = 0; m <
M; m++) {
772 ti += weights[m] * ssx[m].get_ord(n);
780 void enqueue_follower (
long ti,
int m,
int n, T sum) {
781 T next_sum = sum + ssx[m].get_diff(n + 1);
782 long next_ti = ti + weights[m];
783 heap_push<HC> (++heap_size, bh_val, bh_ids, next_sum, next_ti);
796 MultiIndexQuantizer::MultiIndexQuantizer (
int d,
799 Index(d, METRIC_L2), pq(d, M, nbits)
806 void MultiIndexQuantizer::set_typename()
809 s <<
"MI_" << pq.
M <<
"x" << pq.
nbits;
810 index_typename = s.str();
820 for (
int m = 0; m < pq.
M; m++)
826 float *distances,
idx_t *labels)
const {
828 float * dis_tables =
new float [n * pq.
ksub * pq.
M];
838 for (
int i = 0; i < n; i++) {
839 msk.run (dis_tables + i * pq.
ksub * pq.
M,
840 distances + i * k, labels + i * k);
844 delete [] dis_tables;
853 for (
int m = 0; m < pq.
M; m++) {
854 long n = jj % pq.
ksub;
862 for (
int m = 0; m < pq.
M; m++) {
863 long n = jj % pq.
ksub;
867 pq.
decode ((uint8_t*)code, recons);
868 }
else FAISS_ASSERT(!
"only 1 or 2 bytes per index supported");
874 FAISS_ASSERT (!
"This index has virtual elements, it does not support add");
879 FAISS_ASSERT (!
"This index has virtual elements, "
880 "it does not support reset");
int M
nb of elements to sum up
std::vector< uint8_t > codes
Codes. Size ntotal * pq.code_size.
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)
Hamming distance on codes.
bool do_polysemous_training
false = standard PQ
virtual void train(idx_t n, const float *x) override
size_t byte_per_idx
nb bytes per code component (1 or 2)
void partial_sort(int k, int n, const typename C::T *vals, typename C::TI *perm)
void grow(int next_k)
grow the sorted part of the array to size next_k
void compute_distance_tables(size_t nx, const float *x, float *dis_tables) const
void generalized_hammings_knn(int_maxheap_array_t *ha, const uint8_t *a, const uint8_t *b, size_t nb, size_t code_size, int ordered)
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 hamming_distance_histogram(idx_t n, const float *x, idx_t nb, const float *xb, long *dist_histogram)
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
Filter on generalized Hamming.
virtual void reset()
removes all elements from the database.
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
long idx_t
all indices are this type
virtual void train(idx_t n, const float *x)
void hammings_knn(int_maxheap_array_t *ha, const uint8_t *a, const uint8_t *b, size_t nb, size_t ncodes, int order)
virtual void add(idx_t n, const float *x)
add and reset will crash at runtime
ProductQuantizer pq
The product quantizer used to encode the vectors.
idx_t ntotal
total nb of indexed vectors
bool verbose
verbosity level
virtual void add(idx_t n, const float *x) override
int K
nb of sums to return
void hamming_distance_table(idx_t n, const float *x, int32_t *dis) const
virtual void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const
virtual void reconstruct(idx_t key, float *recons) const override
MetricType metric_type
type of metric this index uses for search
size_t M
number of subquantizers
int N
nb of possible elements for each of the M terms
virtual void reconstruct_n(idx_t i0, idx_t ni, float *recons) const override
asymmetric product quantizer (default)
HE filter (using ht) + PQ combination.
bool is_trained
set if the Index does not require training, or if training is done already
virtual void reset() override
removes all elements from the database.
virtual void reconstruct(idx_t key, float *recons) const
void optimize_pq_for_hamming(ProductQuantizer &pq, size_t n, const float *x) const
virtual void search(idx_t n, const float *x, idx_t k, float *distances, idx_t *labels) const override
symmetric product quantizer (SDC)
int polysemous_ht
Hamming threshold used for polysemy.
PolysemousTraining polysemous_training
parameters used for the polysemous training
MetricType
Some algorithms support both an inner product vetsion and a L2 search version.