mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-12 02:13:36 +00:00
Facebook sync (Mar 2019) (#756)
Facebook sync (Mar 2019) - MatrixStats object - option to round coordinates during k-means optimization - alternative option for search in HNSW - moved stats and imbalance_factor of IndexIVF to InvertedLists object - range search for IVFScalarQuantizer - direct unit8 codec in ScalarQuantizer - renamed IndexProxy to IndexReplicas and moved to main Faiss - better support for PQ code assignment with external index - support for IMI2x16 (4B virtual centroids!) - support for k = 2048 search on GPU (instead of 1024) - most CUDA mem alloc failures throw exceptions instead of terminating on an assertion - support for renaming an ondisk invertedlists - interrupt computations with ctrl-C in python
This commit is contained in:
+43
-457
@@ -10,17 +10,22 @@
|
||||
|
||||
#include "MetaIndexes.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "FaissAssert.h"
|
||||
#include "Heap.h"
|
||||
#include "AuxIndexStructures.h"
|
||||
#include "WorkerThread.h"
|
||||
|
||||
|
||||
namespace faiss {
|
||||
|
||||
namespace {
|
||||
|
||||
typedef Index::idx_t idx_t;
|
||||
|
||||
} // namespace
|
||||
|
||||
/*****************************************************
|
||||
* IndexIDMap implementation
|
||||
*******************************************************/
|
||||
@@ -176,405 +181,6 @@ void IndexIDMap2::reconstruct (idx_t key, float * recons) const
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*****************************************************
|
||||
* IndexShards implementation
|
||||
*******************************************************/
|
||||
|
||||
// subroutines
|
||||
namespace {
|
||||
|
||||
|
||||
typedef Index::idx_t idx_t;
|
||||
|
||||
|
||||
template<class Job>
|
||||
struct Thread {
|
||||
Job job;
|
||||
pthread_t thread;
|
||||
|
||||
Thread () {}
|
||||
|
||||
explicit Thread (const Job & job): job(job) {}
|
||||
|
||||
void start () {
|
||||
pthread_create (&thread, nullptr, run, this);
|
||||
}
|
||||
|
||||
void wait () {
|
||||
pthread_join (thread, nullptr);
|
||||
}
|
||||
|
||||
static void * run (void *arg) {
|
||||
static_cast<Thread*> (arg)->job.run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/// callback + thread management to train 1 shard
|
||||
struct TrainJob {
|
||||
IndexShards *index; // the relevant index
|
||||
int no; // shard number
|
||||
idx_t n; // train points
|
||||
const float *x;
|
||||
|
||||
void run ()
|
||||
{
|
||||
if (index->verbose)
|
||||
printf ("begin train shard %d on %ld points\n", no, n);
|
||||
index->shard_indexes [no]->train(n, x);
|
||||
if (index->verbose)
|
||||
printf ("end train shard %d\n", no);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct AddJob {
|
||||
IndexShards *index; // the relevant index
|
||||
int no; // shard number
|
||||
|
||||
idx_t n;
|
||||
const float *x;
|
||||
const idx_t *ids;
|
||||
|
||||
void run ()
|
||||
{
|
||||
if (index->verbose)
|
||||
printf ("begin add shard %d on %ld points\n", no, n);
|
||||
if (ids)
|
||||
index->shard_indexes[no]->add_with_ids (n, x, ids);
|
||||
else
|
||||
index->shard_indexes[no]->add (n, x);
|
||||
if (index->verbose)
|
||||
printf ("end add shard %d on %ld points\n", no, n);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// callback + thread management to query in 1 shard
|
||||
struct QueryJob {
|
||||
const IndexShards *index; // the relevant index
|
||||
int no; // shard number
|
||||
|
||||
// query params
|
||||
idx_t n;
|
||||
const float *x;
|
||||
idx_t k;
|
||||
float *distances;
|
||||
idx_t *labels;
|
||||
|
||||
|
||||
void run ()
|
||||
{
|
||||
if (index->verbose)
|
||||
printf ("begin query shard %d on %ld points\n", no, n);
|
||||
index->shard_indexes [no]->search (n, x, k,
|
||||
distances, labels);
|
||||
if (index->verbose)
|
||||
printf ("end query shard %d\n", no);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// add translation to all valid labels
|
||||
void translate_labels (long n, idx_t *labels, long translation)
|
||||
{
|
||||
if (translation == 0) return;
|
||||
for (long i = 0; i < n; i++) {
|
||||
if(labels[i] < 0) return;
|
||||
labels[i] += translation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** merge result tables from several shards.
|
||||
* @param all_distances size nshard * n * k
|
||||
* @param all_labels idem
|
||||
* @param translartions label translations to apply, size nshard
|
||||
*/
|
||||
|
||||
template <class C>
|
||||
void merge_tables (long n, long k, long nshard,
|
||||
float *distances, idx_t *labels,
|
||||
const float *all_distances,
|
||||
idx_t *all_labels,
|
||||
const long *translations)
|
||||
{
|
||||
if(k == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long stride = n * k;
|
||||
#pragma omp parallel
|
||||
{
|
||||
std::vector<int> buf (2 * nshard);
|
||||
int * pointer = buf.data();
|
||||
int * shard_ids = pointer + nshard;
|
||||
std::vector<float> buf2 (nshard);
|
||||
float * heap_vals = buf2.data();
|
||||
#pragma omp for
|
||||
for (long i = 0; i < n; i++) {
|
||||
// the heap maps values to the shard where they are
|
||||
// produced.
|
||||
const float *D_in = all_distances + i * k;
|
||||
const idx_t *I_in = all_labels + i * k;
|
||||
int heap_size = 0;
|
||||
|
||||
for (long s = 0; s < nshard; s++) {
|
||||
pointer[s] = 0;
|
||||
if (I_in[stride * s] >= 0)
|
||||
heap_push<C> (++heap_size, heap_vals, shard_ids,
|
||||
D_in[stride * s], s);
|
||||
}
|
||||
|
||||
float *D = distances + i * k;
|
||||
idx_t *I = labels + i * k;
|
||||
|
||||
for (int j = 0; j < k; j++) {
|
||||
if (heap_size == 0) {
|
||||
I[j] = -1;
|
||||
D[j] = C::neutral();
|
||||
} else {
|
||||
// pop best element
|
||||
int s = shard_ids[0];
|
||||
int & p = pointer[s];
|
||||
D[j] = heap_vals[0];
|
||||
I[j] = I_in[stride * s + p] + translations[s];
|
||||
|
||||
heap_pop<C> (heap_size--, heap_vals, shard_ids);
|
||||
p++;
|
||||
if (p < k && I_in[stride * s + p] >= 0)
|
||||
heap_push<C> (++heap_size, heap_vals, shard_ids,
|
||||
D_in[stride * s + p], s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
IndexShards::IndexShards (idx_t d, bool threaded, bool successive_ids):
|
||||
Index (d), own_fields (false),
|
||||
threaded (threaded), successive_ids (successive_ids)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IndexShards::add_shard (Index *idx)
|
||||
{
|
||||
shard_indexes.push_back (idx);
|
||||
sync_with_shard_indexes ();
|
||||
}
|
||||
|
||||
void IndexShards::sync_with_shard_indexes ()
|
||||
{
|
||||
if (shard_indexes.empty()) return;
|
||||
Index * index0 = shard_indexes[0];
|
||||
d = index0->d;
|
||||
metric_type = index0->metric_type;
|
||||
is_trained = index0->is_trained;
|
||||
ntotal = index0->ntotal;
|
||||
for (int i = 1; i < shard_indexes.size(); i++) {
|
||||
Index * index = shard_indexes[i];
|
||||
FAISS_THROW_IF_NOT (metric_type == index->metric_type);
|
||||
FAISS_THROW_IF_NOT (d == index->d);
|
||||
ntotal += index->ntotal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IndexShards::train (idx_t n, const float *x)
|
||||
{
|
||||
|
||||
// pre-alloc because we don't want reallocs
|
||||
std::vector<Thread<TrainJob > > tss (shard_indexes.size());
|
||||
int nt = 0;
|
||||
for (int i = 0; i < shard_indexes.size(); i++) {
|
||||
if(!shard_indexes[i]->is_trained) {
|
||||
TrainJob ts = {this, i, n, x};
|
||||
if (threaded) {
|
||||
tss[nt] = Thread<TrainJob> (ts);
|
||||
tss[nt++].start();
|
||||
} else {
|
||||
ts.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nt; i++) {
|
||||
tss[i].wait();
|
||||
}
|
||||
sync_with_shard_indexes ();
|
||||
}
|
||||
|
||||
void IndexShards::add (idx_t n, const float *x)
|
||||
{
|
||||
add_with_ids (n, x, nullptr);
|
||||
}
|
||||
|
||||
|
||||
void IndexShards::add_with_ids (idx_t n, const float * x, const long *xids)
|
||||
{
|
||||
|
||||
FAISS_THROW_IF_NOT_MSG(!(successive_ids && xids),
|
||||
"It makes no sense to pass in ids and "
|
||||
"request them to be shifted");
|
||||
|
||||
if (successive_ids) {
|
||||
FAISS_THROW_IF_NOT_MSG(!xids,
|
||||
"It makes no sense to pass in ids and "
|
||||
"request them to be shifted");
|
||||
FAISS_THROW_IF_NOT_MSG(ntotal == 0,
|
||||
"when adding to IndexShards with sucessive_ids, "
|
||||
"only add() in a single pass is supported");
|
||||
}
|
||||
|
||||
long nshard = shard_indexes.size();
|
||||
const long *ids = xids;
|
||||
ScopeDeleter<long> del;
|
||||
if (!ids && !successive_ids) {
|
||||
long *aids = new long[n];
|
||||
for (long i = 0; i < n; i++)
|
||||
aids[i] = ntotal + i;
|
||||
ids = aids;
|
||||
del.set (ids);
|
||||
}
|
||||
|
||||
std::vector<Thread<AddJob > > asa (shard_indexes.size());
|
||||
int nt = 0;
|
||||
for (int i = 0; i < nshard; i++) {
|
||||
long i0 = i * n / nshard;
|
||||
long i1 = (i + 1) * n / nshard;
|
||||
|
||||
AddJob as = {this, i,
|
||||
i1 - i0, x + i0 * d,
|
||||
ids ? ids + i0 : nullptr};
|
||||
if (threaded) {
|
||||
asa[nt] = Thread<AddJob>(as);
|
||||
asa[nt++].start();
|
||||
} else {
|
||||
as.run();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nt; i++) {
|
||||
asa[i].wait();
|
||||
}
|
||||
ntotal += n;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void IndexShards::reset ()
|
||||
{
|
||||
for (int i = 0; i < shard_indexes.size(); i++) {
|
||||
shard_indexes[i]->reset ();
|
||||
}
|
||||
sync_with_shard_indexes ();
|
||||
}
|
||||
|
||||
void IndexShards::search (
|
||||
idx_t n, const float *x, idx_t k,
|
||||
float *distances, idx_t *labels) const
|
||||
{
|
||||
long nshard = shard_indexes.size();
|
||||
float *all_distances = new float [nshard * k * n];
|
||||
idx_t *all_labels = new idx_t [nshard * k * n];
|
||||
ScopeDeleter<float> del (all_distances);
|
||||
ScopeDeleter<idx_t> del2 (all_labels);
|
||||
|
||||
#if 1
|
||||
|
||||
// pre-alloc because we don't want reallocs
|
||||
std::vector<Thread<QueryJob> > qss (nshard);
|
||||
for (int i = 0; i < nshard; i++) {
|
||||
QueryJob qs = {
|
||||
this, i, n, x, k,
|
||||
all_distances + i * k * n,
|
||||
all_labels + i * k * n
|
||||
};
|
||||
if (threaded) {
|
||||
qss[i] = Thread<QueryJob> (qs);
|
||||
qss[i].start();
|
||||
} else {
|
||||
qs.run();
|
||||
}
|
||||
}
|
||||
|
||||
if (threaded) {
|
||||
for (int i = 0; i < qss.size(); i++) {
|
||||
qss[i].wait();
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
// pre-alloc because we don't want reallocs
|
||||
std::vector<QueryJob> qss (nshard);
|
||||
for (int i = 0; i < nshard; i++) {
|
||||
QueryJob qs = {
|
||||
this, i, n, x, k,
|
||||
all_distances + i * k * n,
|
||||
all_labels + i * k * n
|
||||
};
|
||||
if (threaded) {
|
||||
qss[i] = qs;
|
||||
} else {
|
||||
qs.run();
|
||||
}
|
||||
}
|
||||
|
||||
if (threaded) {
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < qss.size(); i++) {
|
||||
qss[i].run();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
std::vector<long> translations (nshard, 0);
|
||||
if (successive_ids) {
|
||||
translations[0] = 0;
|
||||
for (int s = 0; s + 1 < nshard; s++)
|
||||
translations [s + 1] = translations [s] +
|
||||
shard_indexes [s]->ntotal;
|
||||
}
|
||||
|
||||
if (metric_type == METRIC_L2) {
|
||||
merge_tables< CMin<float, int> > (
|
||||
n, k, nshard, distances, labels,
|
||||
all_distances, all_labels, translations.data ());
|
||||
} else {
|
||||
merge_tables< CMax<float, int> > (
|
||||
n, k, nshard, distances, labels,
|
||||
all_distances, all_labels, translations.data ());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
IndexShards::~IndexShards ()
|
||||
{
|
||||
if (own_fields) {
|
||||
for (int s = 0; s < shard_indexes.size(); s++)
|
||||
delete shard_indexes [s];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************
|
||||
* IndexSplitVectors implementation
|
||||
*******************************************************/
|
||||
@@ -614,44 +220,6 @@ void IndexSplitVectors::add(idx_t /*n*/, const float* /*x*/) {
|
||||
FAISS_THROW_MSG("not implemented");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// callback + thread management to query in 1 shard
|
||||
struct SplitQueryJob {
|
||||
const IndexSplitVectors *index; // the relevant index
|
||||
int no; // shard number
|
||||
|
||||
// query params
|
||||
idx_t n;
|
||||
const float *x;
|
||||
idx_t k;
|
||||
float *distances;
|
||||
idx_t *labels;
|
||||
|
||||
|
||||
void run ()
|
||||
{
|
||||
if (index->verbose)
|
||||
printf ("begin query shard %d on %ld points\n", no, n);
|
||||
const Index * sub_index = index->sub_indexes[no];
|
||||
long sub_d = sub_index->d, d = index->d;
|
||||
idx_t ofs = 0;
|
||||
for (int i = 0; i < no; i++) ofs += index->sub_indexes[i]->d;
|
||||
float *sub_x = new float [sub_d * n];
|
||||
ScopeDeleter<float> del (sub_x);
|
||||
for (idx_t i = 0; i < n; i++)
|
||||
memcpy (sub_x + i * sub_d, x + ofs + i * d, sub_d * sizeof (sub_x));
|
||||
sub_index->search (n, sub_x, k, distances, labels);
|
||||
if (index->verbose)
|
||||
printf ("end query shard %d\n", no);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void IndexSplitVectors::search (
|
||||
@@ -669,25 +237,43 @@ void IndexSplitVectors::search (
|
||||
ScopeDeleter<float> del (all_distances);
|
||||
ScopeDeleter<idx_t> del2 (all_labels);
|
||||
|
||||
// pre-alloc because we don't want reallocs
|
||||
std::vector<Thread<SplitQueryJob> > qss (nshard);
|
||||
for (int i = 0; i < nshard; i++) {
|
||||
SplitQueryJob qs = {
|
||||
this, i, n, x, k,
|
||||
i == 0 ? distances : all_distances + i * k * n,
|
||||
i == 0 ? labels : all_labels + i * k * n
|
||||
};
|
||||
if (threaded) {
|
||||
qss[i] = Thread<SplitQueryJob> (qs);
|
||||
qss[i].start();
|
||||
} else {
|
||||
qs.run();
|
||||
}
|
||||
}
|
||||
auto query_func = [n, x, k, distances, labels, all_distances, all_labels, this]
|
||||
(int no) {
|
||||
const IndexSplitVectors *index = this;
|
||||
float *distances1 = no == 0 ? distances : all_distances + no * k * n;
|
||||
idx_t *labels1 = no == 0 ? labels : all_labels + no * k * n;
|
||||
if (index->verbose)
|
||||
printf ("begin query shard %d on %ld points\n", no, n);
|
||||
const Index * sub_index = index->sub_indexes[no];
|
||||
long sub_d = sub_index->d, d = index->d;
|
||||
idx_t ofs = 0;
|
||||
for (int i = 0; i < no; i++) ofs += index->sub_indexes[i]->d;
|
||||
float *sub_x = new float [sub_d * n];
|
||||
ScopeDeleter<float> del1 (sub_x);
|
||||
for (idx_t i = 0; i < n; i++)
|
||||
memcpy (sub_x + i * sub_d, x + ofs + i * d, sub_d * sizeof (sub_x));
|
||||
sub_index->search (n, sub_x, k, distances1, labels1);
|
||||
if (index->verbose)
|
||||
printf ("end query shard %d\n", no);
|
||||
};
|
||||
|
||||
if (threaded) {
|
||||
for (int i = 0; i < qss.size(); i++) {
|
||||
qss[i].wait();
|
||||
if (!threaded) {
|
||||
for (int i = 0; i < nshard; i++) {
|
||||
query_func(i);
|
||||
}
|
||||
} else {
|
||||
std::vector<std::unique_ptr<WorkerThread> > threads;
|
||||
std::vector<std::future<bool>> v;
|
||||
|
||||
for (int i = 0; i < nshard; i++) {
|
||||
threads.emplace_back(new WorkerThread());
|
||||
WorkerThread *wt = threads.back().get();
|
||||
v.emplace_back(wt->add([i, query_func](){query_func(i); }));
|
||||
}
|
||||
|
||||
// Blocking wait for completion
|
||||
for (auto& func : v) {
|
||||
func.get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user