Skip to content

Commit

Permalink
Merge commit b109d086a2a200b1697481dd3d79faedc585a623 from Faiss mast…
Browse files Browse the repository at this point in the history
…er branch

Summary:
This PR adds a functionality where an IVF index can be searched and the corresponding codes be returned. It also adds a few functions to compress int arrays into a bit-compact representation.

Pull Request resolved: facebookresearch/faiss#3143

Test Plan:
```
buck test //faiss/tests/:test_index_composite -- TestSearchAndReconstruct

buck test //faiss/tests/:test_standalone_codec -- test_arrays
```

Reviewed By: algoriddle

Differential Revision: D51544613

Pulled By: mdouze

fbshipit-source-id: 875f72d0f9140096851592422570efa0f65431fc
Signed-off-by: Alexandr Guzhva <[email protected]>
  • Loading branch information
alexanderguzhva committed Nov 27, 2023
1 parent f62a98e commit 4630feb
Show file tree
Hide file tree
Showing 12 changed files with 785 additions and 278 deletions.
582 changes: 341 additions & 241 deletions thirdparty/faiss/benchs/bench_all_ivf/bench_all_ivf.py

Large diffs are not rendered by default.

9 changes: 1 addition & 8 deletions thirdparty/faiss/benchs/bench_hybrid_cpu_gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,14 +530,7 @@ def aa(*args, **kwargs):
raise RuntimeError()

totex = op.num_experiments()
rs = np.random.RandomState(123)
if totex < args.n_autotune:
experiments = rs.permutation(totex - 2) + 1
else:
experiments = rs.randint(
totex - 2, size=args.n_autotune - 2, replace=False)

experiments = [0, totex - 1] + list(experiments)
experiments = op.sample_experiments()
print(f"total nb experiments {totex}, running {len(experiments)}")

print("perform search")
Expand Down
18 changes: 17 additions & 1 deletion thirdparty/faiss/contrib/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,23 @@ def do_nothing_key(self):
return np.zeros(len(self.ranges), dtype=int)

def num_experiments(self):
return np.prod([len(values) for name, values in self.ranges])
return int(np.prod([len(values) for name, values in self.ranges]))

def sample_experiments(self, n_autotune, rs=np.random):
""" sample a set of experiments of max size n_autotune
(run all experiments in random order if n_autotune is 0)
"""
assert n_autotune == 0 or n_autotune >= 2
totex = self.num_experiments()
rs = np.random.RandomState(123)
if n_autotune == 0 or totex < n_autotune:
experiments = rs.permutation(totex - 2)
else:
experiments = rs.choice(
totex - 2, size=n_autotune - 2, replace=False)

experiments = [0, totex - 1] + [int(cno) + 1 for cno in experiments]
return experiments

def cno_to_key(self, cno):
"""Convert a sequential experiment number to a key"""
Expand Down
109 changes: 85 additions & 24 deletions thirdparty/faiss/faiss/IndexIVF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1005,46 +1005,107 @@ void IndexIVF::search_and_reconstruct(
std::min(nlist, params ? params->nprobe : this->nprobe);
FAISS_THROW_IF_NOT(nprobe > 0);

// todo aguzhva: deprecate ScopeDeleter and ScopeDeleter1
// in favor of std::unique_ptr
idx_t* idx = new idx_t[n * nprobe];
ScopeDeleter<idx_t> del(idx);
float* coarse_dis = new float[n * nprobe];
ScopeDeleter<float> del2(coarse_dis);
std::unique_ptr<idx_t[]> idx(new idx_t[n * nprobe]);
std::unique_ptr<float[]> coarse_dis(new float[n * nprobe]);

quantizer->search(n, x, nprobe, coarse_dis, idx);
quantizer->search(n, x, nprobe, coarse_dis.get(), idx.get());

invlists->prefetch_lists(idx, n * nprobe);
invlists->prefetch_lists(idx.get(), n * nprobe);

// search_preassigned() with `store_pairs` enabled to obtain the list_no
// and offset into `codes` for reconstruction
search_preassigned(
n,
x,
k,
idx,
coarse_dis,
idx.get(),
coarse_dis.get(),
distances,
labels,
true /* store_pairs */,
params);
for (idx_t i = 0; i < n; ++i) {
for (idx_t j = 0; j < k; ++j) {
idx_t ij = i * k + j;
idx_t key = labels[ij];
float* reconstructed = recons + ij * d;
if (key < 0) {
// Fill with NaNs
memset(reconstructed, -1, sizeof(*reconstructed) * d);
} else {
int list_no = lo_listno(key);
int offset = lo_offset(key);
#pragma omp parallel for if (n * k > 1000)
for (idx_t ij = 0; ij < n * k; ij++) {
idx_t key = labels[ij];
float* reconstructed = recons + ij * d;
if (key < 0) {
// Fill with NaNs
memset(reconstructed, -1, sizeof(*reconstructed) * d);
} else {
int list_no = lo_listno(key);
int offset = lo_offset(key);

// Update label to the actual id
labels[ij] = invlists->get_single_id(list_no, offset);

reconstruct_from_offset(list_no, offset, reconstructed);
}
}
}

void IndexIVF::search_and_return_codes(
idx_t n,
const float* x,
idx_t k,
float* distances,
idx_t* labels,
uint8_t* codes,
bool include_listno,
const SearchParameters* params_in) const {
const IVFSearchParameters* params = nullptr;
if (params_in) {
params = dynamic_cast<const IVFSearchParameters*>(params_in);
FAISS_THROW_IF_NOT_MSG(params, "IndexIVF params have incorrect type");
}
const size_t nprobe =
std::min(nlist, params ? params->nprobe : this->nprobe);
FAISS_THROW_IF_NOT(nprobe > 0);

std::unique_ptr<idx_t[]> idx(new idx_t[n * nprobe]);
std::unique_ptr<float[]> coarse_dis(new float[n * nprobe]);

quantizer->search(n, x, nprobe, coarse_dis.get(), idx.get());

invlists->prefetch_lists(idx.get(), n * nprobe);

// search_preassigned() with `store_pairs` enabled to obtain the list_no
// and offset into `codes` for reconstruction
search_preassigned(
n,
x,
k,
idx.get(),
coarse_dis.get(),
distances,
labels,
true /* store_pairs */,
params);

size_t code_size_1 = code_size;
if (include_listno) {
code_size_1 += coarse_code_size();
}

#pragma omp parallel for if (n * k > 1000)
for (idx_t ij = 0; ij < n * k; ij++) {
idx_t key = labels[ij];
uint8_t* code1 = codes + ij * code_size_1;

if (key < 0) {
// Fill with 0xff
memset(code1, -1, code_size_1);
} else {
int list_no = lo_listno(key);
int offset = lo_offset(key);
const uint8_t* cc = invlists->get_single_code(list_no, offset);

// Update label to the actual id
labels[ij] = invlists->get_single_id(list_no, offset);
labels[ij] = invlists->get_single_id(list_no, offset);

reconstruct_from_offset(list_no, offset, reconstructed);
if (include_listno) {
encode_listno(list_no, code1);
code1 += code_size_1 - code_size;
}
memcpy(code1, cc, code_size);
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions thirdparty/faiss/faiss/IndexIVF.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,24 @@ struct IndexIVF : Index, IndexIVFInterface {
float* recons,
const SearchParameters* params = nullptr) const override;

/** Similar to search, but also returns the codes corresponding to the
* stored vectors for the search results.
*
* @param codes codes (n, k, code_size)
* @param include_listno
* include the list ids in the code (in this case add
* ceil(log8(nlist)) to the code size)
*/
void search_and_return_codes(
idx_t n,
const float* x,
idx_t k,
float* distances,
idx_t* labels,
uint8_t* recons,
bool include_listno = false,
const SearchParameters* params = nullptr) const;

/** Reconstruct a vector given the location in terms of (inv list index +
* inv list offset) instead of the id.
*
Expand Down
1 change: 1 addition & 0 deletions thirdparty/faiss/faiss/IndexIVFAdditiveQuantizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ struct AQInvertedListScanner : InvertedListScanner {
const float* q;
/// following codes come from this inverted list
void set_list(idx_t list_no, float coarse_dis) override {
this->list_no = list_no;
if (ia.metric_type == METRIC_L2 && ia.by_residual) {
ia.quantizer->compute_residual(q0, tmp.data(), list_no);
q = tmp.data();
Expand Down
2 changes: 1 addition & 1 deletion thirdparty/faiss/faiss/impl/AdditiveQuantizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ void AdditiveQuantizer::decode(const uint8_t* code, float* x, size_t n) const {
is_trained, "The additive quantizer is not trained yet.");

// standard additive quantizer decoding
#pragma omp parallel for if (n > 1000)
#pragma omp parallel for if (n > 100)
for (int64_t i = 0; i < n; i++) {
BitstringReader bsr(code + i * code_size, code_size);
float* xi = x + i * d;
Expand Down
3 changes: 2 additions & 1 deletion thirdparty/faiss/faiss/impl/ProductQuantizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ void ProductQuantizer::decode(const uint8_t* code, float* x) const {
}

void ProductQuantizer::decode(const uint8_t* code, float* x, size_t n) const {
for (size_t i = 0; i < n; i++) {
#pragma omp parallel for if (n > 100)
for (int64_t i = 0; i < n; i++) {
this->decode(code + code_size * i, x + d * i);
}
}
Expand Down
84 changes: 84 additions & 0 deletions thirdparty/faiss/faiss/utils/hamming.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -686,4 +686,88 @@ void generalized_hammings_knn_hc(
ha->reorder();
}

void pack_bitstrings(
size_t n,
size_t M,
int nbit,
const int32_t* unpacked,
uint8_t* packed,
size_t code_size) {
FAISS_THROW_IF_NOT(code_size >= (M * nbit + 7) / 8);
#pragma omp parallel for if (n > 1000)
for (int64_t i = 0; i < n; i++) {
const int32_t* in = unpacked + i * M;
uint8_t* out = packed + i * code_size;
BitstringWriter wr(out, code_size);
for (int j = 0; j < M; j++) {
wr.write(in[j], nbit);
}
}
}

void pack_bitstrings(
size_t n,
size_t M,
const int32_t* nbit,
const int32_t* unpacked,
uint8_t* packed,
size_t code_size) {
int totbit = 0;
for (int j = 0; j < M; j++) {
totbit += nbit[j];
}
FAISS_THROW_IF_NOT(code_size >= (totbit + 7) / 8);
#pragma omp parallel for if (n > 1000)
for (int64_t i = 0; i < n; i++) {
const int32_t* in = unpacked + i * M;
uint8_t* out = packed + i * code_size;
BitstringWriter wr(out, code_size);
for (int j = 0; j < M; j++) {
wr.write(in[j], nbit[j]);
}
}
}

void unpack_bitstrings(
size_t n,
size_t M,
int nbit,
const uint8_t* packed,
size_t code_size,
int32_t* unpacked) {
FAISS_THROW_IF_NOT(code_size >= (M * nbit + 7) / 8);
#pragma omp parallel for if (n > 1000)
for (int64_t i = 0; i < n; i++) {
const uint8_t* in = packed + i * code_size;
int32_t* out = unpacked + i * M;
BitstringReader rd(in, code_size);
for (int j = 0; j < M; j++) {
out[j] = rd.read(nbit);
}
}
}

void unpack_bitstrings(
size_t n,
size_t M,
const int32_t* nbit,
const uint8_t* packed,
size_t code_size,
int32_t* unpacked) {
int totbit = 0;
for (int j = 0; j < M; j++) {
totbit += nbit[j];
}
FAISS_THROW_IF_NOT(code_size >= (totbit + 7) / 8);
#pragma omp parallel for if (n > 1000)
for (int64_t i = 0; i < n; i++) {
const uint8_t* in = packed + i * code_size;
int32_t* out = unpacked + i * M;
BitstringReader rd(in, code_size);
for (int j = 0; j < M; j++) {
out[j] = rd.read(nbit[j]);
}
}
}

} // namespace faiss
58 changes: 58 additions & 0 deletions thirdparty/faiss/faiss/utils/hamming.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,64 @@ void generalized_hammings_knn_hc(
size_t code_size,
int ordered = true);

/** Pack a set of n codes of size M * nbit
*
* @param n number of codes to pack
* @param M number of elementary codes per code
* @param nbit number of bits per elementary code
* @param unpacked input unpacked codes, size (n, M)
* @param packed output packed codes, size (n, code_size)
* @param code_size should be >= ceil(M * nbit / 8)
*/
void pack_bitstrings(
size_t n,
size_t M,
int nbit,
const int32_t* unpacked,
uint8_t* packed,
size_t code_size);

/** Pack a set of n codes of variable sizes
*
* @param nbit number of bits per entry (size M)
*/
void pack_bitstrings(
size_t n,
size_t M,
const int32_t* nbits,
const int32_t* unpacked,
uint8_t* packed,
size_t code_size);

/** Unpack a set of n codes of size M * nbit
*
* @param n number of codes to pack
* @param M number of elementary codes per code
* @param nbit number of bits per elementary code
* @param unpacked input unpacked codes, size (n, M)
* @param packed output packed codes, size (n, code_size)
* @param code_size should be >= ceil(M * nbit / 8)
*/
void unpack_bitstrings(
size_t n,
size_t M,
int nbit,
const uint8_t* packed,
size_t code_size,
int32_t* unpacked);

/** Unpack a set of n codes of variable sizes
*
* @param nbit number of bits per entry (size M)
*/
void unpack_bitstrings(
size_t n,
size_t M,
const int32_t* nbits,
const uint8_t* packed,
size_t code_size,
int32_t* unpacked);

} // namespace faiss

#include <faiss/utils/hamming-inl.h>
Expand Down
Loading

0 comments on commit 4630feb

Please sign in to comment.