Facebook sync 2020-07-31 (#1308)

This commit is contained in:
Lucas Hosseini
2020-08-03 22:15:02 +02:00
committed by GitHub
parent a93a4b3957
commit cd38e82f0c
145 changed files with 3885 additions and 3035 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ struct AutoTuneCriterion {
typedef Index::idx_t idx_t;
idx_t nq; ///< nb of queries this criterion is evaluated on
idx_t nnn; ///< nb of NNs that the query should request
idx_t gt_nnn; ///< nb of GT NNs required to evaluate criterion
idx_t gt_nnn; ///< nb of GT NNs required to evaluate crterion
std::vector<float> gt_D; ///< Ground-truth distances (size nq * gt_nnn)
std::vector<idx_t> gt_I; ///< Ground-truth indexes (size nq * gt_nnn)
-1
View File
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
// Copyright 2004-present Facebook. All Rights Reserved
// -*- c++ -*-
#include <faiss/IndexBinaryHash.h>
-1
View File
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
// Copyright 2004-present Facebook. All Rights Reserved
// -*- c++ -*-
#include <faiss/IndexBinaryIVF.h>
+25 -22
View File
@@ -289,7 +289,7 @@ void IndexHNSW::search (idx_t n, const float *x, idx_t k,
{
FAISS_THROW_IF_NOT_MSG(storage,
"Please use IndexHSNWFlat (or variants) instead of IndexHNSW directly");
size_t nreorder = 0;
HNSWStats stats;
idx_t check_period = InterruptCallback::get_period_hint (
hnsw.max_level * d * hnsw.efSearch);
@@ -297,21 +297,22 @@ void IndexHNSW::search (idx_t n, const float *x, idx_t k,
for (idx_t i0 = 0; i0 < n; i0 += check_period) {
idx_t i1 = std::min(i0 + check_period, n);
#pragma omp parallel reduction(+ : nreorder)
#pragma omp parallel
{
VisitedTable vt (ntotal);
DistanceComputer *dis = storage_distance_computer(storage);
ScopeDeleter1<DistanceComputer> del(dis);
#pragma omp for
#pragma omp declare reduction (stats_combine : HNSWStats : omp_out.combine(omp_in))
#pragma omp for reduction (stats_combine : stats)
for(idx_t i = i0; i < i1; i++) {
idx_t * idxi = labels + i * k;
float * simi = distances + i * k;
dis->set_query(x + i * d);
maxheap_heapify (k, simi, idxi);
hnsw.search(*dis, k, idxi, simi, vt);
stats.combine(hnsw.search(*dis, k, idxi, simi, vt));
maxheap_reorder (k, simi, idxi);
@@ -320,7 +321,7 @@ void IndexHNSW::search (idx_t n, const float *x, idx_t k,
int k_reorder = reconstruct_from_neighbors->k_reorder;
if (k_reorder == -1 || k_reorder > k) k_reorder = k;
nreorder += reconstruct_from_neighbors->compute_distances(
stats.nreorder += reconstruct_from_neighbors->compute_distances(
k_reorder, idxi, x + i * d, simi);
// sort top k_reorder
@@ -341,7 +342,7 @@ void IndexHNSW::search (idx_t n, const float *x, idx_t k,
}
}
hnsw_stats.nreorder += nreorder;
hnsw_stats.combine(stats);
}
@@ -416,6 +417,8 @@ void IndexHNSW::search_level_0(
{
storage_idx_t ntotal = hnsw.levels.size();
HNSWStats stats;
#pragma omp parallel
{
DistanceComputer *qdis = storage_distance_computer(storage);
@@ -423,7 +426,8 @@ void IndexHNSW::search_level_0(
VisitedTable vt (ntotal);
#pragma omp for
#pragma omp declare reduction (stats_combine : HNSWStats : omp_out.combine(omp_in))
#pragma omp for reduction (stats_combine : stats)
for(idx_t i = 0; i < n; i++) {
idx_t * idxi = labels + i * k;
float * simi = distances + i * k;
@@ -449,7 +453,7 @@ void IndexHNSW::search_level_0(
nres = hnsw.search_from_candidates(
*qdis, k, idxi, simi,
candidates, vt, 0, nres
candidates, vt, stats, 0, nres
);
}
} else if (search_type == 2) {
@@ -466,7 +470,7 @@ void IndexHNSW::search_level_0(
}
hnsw.search_from_candidates(
*qdis, k, idxi, simi,
candidates, vt, 0
candidates, vt, stats, 0
);
}
@@ -477,7 +481,7 @@ void IndexHNSW::search_level_0(
}
}
hnsw_stats.combine(stats);
}
void IndexHNSW::init_level_0_from_knngraph(
@@ -948,6 +952,7 @@ int search_from_candidates_2(const HNSW & hnsw,
idx_t *I, float * D,
MinimaxHeap &candidates,
VisitedTable &vt,
HNSWStats &stats,
int level, int nres_in = 0)
{
int nres = nres_in;
@@ -996,15 +1001,9 @@ int search_from_candidates_2(const HNSW & hnsw,
}
}
if (level == 0) {
#pragma omp critical
{
hnsw_stats.n1 ++;
if (candidates.size() == 0)
hnsw_stats.n2 ++;
}
}
stats.n1 ++;
if (candidates.size() == 0)
stats.n2 ++;
return nres;
}
@@ -1019,6 +1018,7 @@ void IndexHNSW2Level::search (idx_t n, const float *x, idx_t k,
IndexHNSW::search (n, x, k, distances, labels);
} else { // "mixed" search
HNSWStats stats;
const IndexIVFPQ *index_ivfpq =
dynamic_cast<const IndexIVFPQ*>(storage);
@@ -1044,7 +1044,8 @@ void IndexHNSW2Level::search (idx_t n, const float *x, idx_t k,
int candidates_size = hnsw.upper_beam;
MinimaxHeap candidates(candidates_size);
#pragma omp for
#pragma omp declare reduction (stats_combine : HNSWStats : omp_out.combine(omp_in))
#pragma omp for reduction (stats_combine : stats)
for(idx_t i = 0; i < n; i++) {
idx_t * idxi = labels + i * k;
float * simi = distances + i * k;
@@ -1083,7 +1084,7 @@ void IndexHNSW2Level::search (idx_t n, const float *x, idx_t k,
hnsw.search_from_candidates(
*dis, k, idxi, simi,
candidates, vt, 0, k
candidates, vt, stats, 0, k
);
vt.advance();
@@ -1100,7 +1101,7 @@ void IndexHNSW2Level::search (idx_t n, const float *x, idx_t k,
search_from_candidates_2 (
hnsw, *dis, k, idxi, simi,
candidates, vt, 0, k);
candidates, vt, stats, 0, k);
vt.advance ();
vt.advance ();
@@ -1109,6 +1110,8 @@ void IndexHNSW2Level::search (idx_t n, const float *x, idx_t k,
maxheap_reorder (k, simi, idxi);
}
}
hnsw_stats.combine(stats);
}
+48 -13
View File
@@ -300,10 +300,10 @@ void IndexIVF::search_preassigned (idx_t n, const float *x, idx_t k,
bool do_heap_init = !(this->parallel_mode & PARALLEL_MODE_NO_HEAP_INIT);
// don't start parallel section if single query
bool do_parallel =
pmode == 0 ? n > 1 :
pmode == 1 ? nprobe > 1 :
nprobe * n > 1;
bool do_parallel = omp_get_max_threads() >= 2 && (
pmode == 0 ? n > 1 :
pmode == 1 ? nprobe > 1 :
nprobe * n > 1);
#pragma omp parallel if(do_parallel) reduction(+: nlistv, ndis, nheap)
{
@@ -327,6 +327,19 @@ void IndexIVF::search_preassigned (idx_t n, const float *x, idx_t k,
}
};
auto add_local_results = [&](
const float * local_dis, const idx_t * local_idx,
float *simi, idx_t *idxi)
{
if (metric_type == METRIC_INNER_PRODUCT) {
heap_addn<HeapForIP>
(k, simi, idxi, local_dis, local_idx, k);
} else {
heap_addn<HeapForL2>
(k, simi, idxi, local_dis, local_idx, k);
}
};
auto reorder_result = [&] (float *simi, idx_t *idxi) {
if (!do_heap_init) return;
if (metric_type == METRIC_INNER_PRODUCT) {
@@ -447,20 +460,42 @@ void IndexIVF::search_preassigned (idx_t n, const float *x, idx_t k,
#pragma omp barrier
#pragma omp critical
{
if (metric_type == METRIC_INNER_PRODUCT) {
heap_addn<HeapForIP>
(k, simi, idxi,
local_dis.data(), local_idx.data(), k);
} else {
heap_addn<HeapForL2>
(k, simi, idxi,
local_dis.data(), local_idx.data(), k);
}
add_local_results (local_dis.data(), local_idx.data(),
simi, idxi);
}
#pragma omp barrier
#pragma omp single
reorder_result (simi, idxi);
}
} else if (pmode == 2) {
std::vector <idx_t> local_idx (k);
std::vector <float> local_dis (k);
#pragma omp single
for (size_t i = 0; i < n; i++) {
init_result (distances + i * k, labels + i * k);
}
#pragma omp for schedule(dynamic)
for (size_t ij = 0; ij < n * nprobe; ij++) {
size_t i = ij / nprobe;
size_t j = ij % nprobe;
scanner->set_query (x + i * d);
init_result (local_dis.data(), local_idx.data());
ndis += scan_one_list (
keys [ij], coarse_dis[ij],
local_dis.data(), local_idx.data());
#pragma omp critical
{
add_local_results (local_dis.data(), local_idx.data(),
distances + i * k, labels + i * k);
}
}
#pragma omp single
for (size_t i = 0; i < n; i++) {
reorder_result (distances + i * k, labels + i * k);
}
} else {
FAISS_THROW_FMT ("parallel_mode %d not supported\n",
pmode);
+1
View File
@@ -68,6 +68,7 @@ struct Level1Quantizer {
struct IVFSearchParameters {
size_t nprobe; ///< number of probes at query time
size_t max_codes; ///< max nb of codes to visit to do a query
IVFSearchParameters(): nprobe(1), max_codes(0) {}
virtual ~IVFSearchParameters () {}
};
+1
View File
@@ -22,6 +22,7 @@ namespace faiss {
struct IVFPQSearchParameters: IVFSearchParameters {
size_t scan_table_threshold; ///< use table computation or on-the-fly?
int polysemous_ht; ///< Hamming thresh for polysemous filtering
IVFPQSearchParameters (): scan_table_threshold(0), polysemous_ht(0) {}
~IVFPQSearchParameters () {}
};
+1
View File
@@ -208,6 +208,7 @@ struct ArrayInvertedLists: InvertedLists {
* that can be stacked horizontally, vertically and sliced.
*****************************************************************/
/// invlists that fail for all write functions
struct ReadOnlyInvertedLists: InvertedLists {
ReadOnlyInvertedLists (size_t nlist, size_t code_size):
+139 -1
View File
@@ -16,10 +16,14 @@
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <faiss/impl/FaissAssert.h>
#include <faiss/utils/utils.h>
#include <faiss/impl/io.h>
#include <faiss/impl/io_macros.h>
namespace faiss {
@@ -341,7 +345,7 @@ void OnDiskInvertedLists::update_totsize (size_t new_size)
#define INVALID_OFFSET (size_t)(-1)
OnDiskInvertedLists::List::List ():
OnDiskOneList::OnDiskOneList ():
size (0), capacity (0), offset (INVALID_OFFSET)
{}
@@ -656,6 +660,12 @@ size_t OnDiskInvertedLists::merge_from (const InvertedLists **ils, int n_il,
}
size_t OnDiskInvertedLists::merge_from_1 (const InvertedLists *ils, bool verbose)
{
return merge_from (&ils, 1, verbose);
}
void OnDiskInvertedLists::crop_invlists(size_t l0, size_t l1)
{
FAISS_THROW_IF_NOT(0 <= l0 && l0 <= l1 && l1 <= nlist);
@@ -668,6 +678,134 @@ void OnDiskInvertedLists::crop_invlists(size_t l0, size_t l1)
nlist = l1 - l0;
}
/*******************************************************
* I/O support via callbacks
*******************************************************/
OnDiskInvertedListsIOHook::OnDiskInvertedListsIOHook():
InvertedListsIOHook("ilod", typeid(OnDiskInvertedLists).name())
{}
void OnDiskInvertedListsIOHook::write(const InvertedLists *ils, IOWriter *f) const
{
uint32_t h = fourcc ("ilod");
WRITE1 (h);
WRITE1 (ils->nlist);
WRITE1 (ils->code_size);
const OnDiskInvertedLists *od = dynamic_cast<const OnDiskInvertedLists*> (ils);
// this is a POD object
WRITEVECTOR (od->lists);
{
std::vector<OnDiskInvertedLists::Slot> v(
od->slots.begin(), od->slots.end());
WRITEVECTOR(v);
}
{
std::vector<char> x(od->filename.begin(), od->filename.end());
WRITEVECTOR(x);
}
WRITE1(od->totsize);
}
InvertedLists * OnDiskInvertedListsIOHook::read(IOReader *f, int io_flags) const
{
OnDiskInvertedLists *od = new OnDiskInvertedLists();
od->read_only = io_flags & IO_FLAG_READ_ONLY;
READ1 (od->nlist);
READ1 (od->code_size);
// this is a POD object
READVECTOR (od->lists);
{
std::vector<OnDiskInvertedLists::Slot> v;
READVECTOR(v);
od->slots.assign(v.begin(), v.end());
}
{
std::vector<char> x;
READVECTOR(x);
od->filename.assign(x.begin(), x.end());
if (io_flags & IO_FLAG_ONDISK_SAME_DIR) {
FileIOReader *reader = dynamic_cast<FileIOReader*>(f);
FAISS_THROW_IF_NOT_MSG (
reader, "IO_FLAG_ONDISK_SAME_DIR only supported "
"when reading from file");
std::string indexname = reader->name;
std::string dirname = "./";
size_t slash = indexname.find_last_of('/');
if (slash != std::string::npos) {
dirname = indexname.substr(0, slash + 1);
}
std::string filename = od->filename;
slash = filename.find_last_of('/');
if (slash != std::string::npos) {
filename = filename.substr(slash + 1);
}
filename = dirname + filename;
printf("IO_FLAG_ONDISK_SAME_DIR: "
"updating ondisk filename from %s to %s\n",
od->filename.c_str(), filename.c_str());
od->filename = filename;
}
}
READ1(od->totsize);
od->do_mmap();
return od;
}
/** read from a ArrayInvertedLists into this invertedlist type */
InvertedLists * OnDiskInvertedListsIOHook::read_ArrayInvertedLists(
IOReader *f, int /* io_flags */,
size_t nlist, size_t code_size,
const std::vector<size_t> &sizes) const
{
auto ails = new OnDiskInvertedLists ();
ails->nlist = nlist;
ails->code_size = code_size;
ails->read_only = true;
ails->lists.resize (nlist);
FileIOReader *reader = dynamic_cast<FileIOReader*>(f);
FAISS_THROW_IF_NOT_MSG(reader, "mmap only supported for File objects");
FILE *fdesc = reader->f;
size_t o0 = ftell(fdesc);
size_t o = o0;
{ // do the mmap
struct stat buf;
int ret = fstat (fileno(fdesc), &buf);
FAISS_THROW_IF_NOT_FMT (ret == 0,
"fstat failed: %s", strerror(errno));
ails->totsize = buf.st_size;
ails->ptr = (uint8_t*)mmap (nullptr, ails->totsize,
PROT_READ, MAP_SHARED,
fileno(fdesc), 0);
FAISS_THROW_IF_NOT_FMT (ails->ptr != MAP_FAILED,
"could not mmap: %s",
strerror(errno));
}
FAISS_THROW_IF_NOT(o <= ails->totsize);
for (size_t i = 0; i < ails->nlist; i++) {
OnDiskInvertedLists::List & l = ails->lists[i];
l.size = l.capacity = sizes[i];
l.offset = o;
o += l.size * (sizeof(OnDiskInvertedLists::idx_t) +
ails->code_size);
}
// resume normal reading of file
fseek (fdesc, o, SEEK_SET);
return ails;
}
+27 -7
View File
@@ -12,14 +12,26 @@
#include <vector>
#include <list>
#include <typeinfo>
#include <faiss/IndexIVF.h>
#include <faiss/index_io.h>
namespace faiss {
struct LockLevels;
struct OnDiskOneList {
size_t size; // size of inverted list (entries)
size_t capacity; // allocated size (entries)
size_t offset; // offset in buffer (bytes)
OnDiskOneList ();
};
/** On-disk storage of inverted lists.
*
* The data is stored in a mmapped chunk of memory (base ptointer ptr,
@@ -49,13 +61,7 @@ struct LockLevels;
* lists in parallel.
*/
struct OnDiskInvertedLists: InvertedLists {
struct List {
size_t size; // size of inverted list (entries)
size_t capacity; // allocated size (entries)
size_t offset; // offset in buffer (bytes)
List ();
};
using List = OnDiskOneList;
// size nlist
std::vector<List> lists;
@@ -95,6 +101,9 @@ struct OnDiskInvertedLists: InvertedLists {
// allocating slots)
size_t merge_from (const InvertedLists **ils, int n_il, bool verbose=false);
/// same as merge_from for a single invlist
size_t merge_from_1 (const InvertedLists *il, bool verbose=false);
/// restrict the inverted lists to l0:l1 without touching the mmapped region
void crop_invlists(size_t l0, size_t l1);
@@ -121,6 +130,17 @@ struct OnDiskInvertedLists: InvertedLists {
OnDiskInvertedLists ();
};
struct OnDiskInvertedListsIOHook: InvertedListsIOHook {
OnDiskInvertedListsIOHook();
void write(const InvertedLists *ils, IOWriter *f) const override;
InvertedLists * read(IOReader *f, int io_flags) const override;
InvertedLists * read_ArrayInvertedLists(
IOReader *f, int io_flags,
size_t nlist, size_t code_size,
const std::vector<size_t> &sizes) const override;
};
} // namespace faiss
+80
View File
@@ -0,0 +1,80 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <cstdio>
#include <omp.h>
#include <faiss/impl/ScalarQuantizer.h>
#include <faiss/utils/utils.h>
#include <faiss/utils/random.h>
#include <faiss/utils/distances.h>
using namespace faiss;
int main() {
int d = 128;
int n = 2000;
std::vector<float> x(d * n);
float_rand(x.data(), d * n, 12345);
// make sure it's idempotent
ScalarQuantizer sq(d, ScalarQuantizer::QT_6bit);
omp_set_num_threads(1);
sq.train(n, x.data());
size_t code_size = sq.code_size;
printf("code size: %ld\n", sq.code_size);
// encode
std::vector<uint8_t> codes(code_size * n);
sq.compute_codes(x.data(), codes.data(), n);
// decode
std::vector<float> x2(d * n);
sq.decode(codes.data(), x2.data(), n);
printf("sqL2 recons error: %g\n",
fvec_L2sqr(x.data(), x2.data(), n * d) / n);
// encode again
std::vector<uint8_t> codes2(code_size * n);
sq.compute_codes(x2.data(), codes2.data(), n);
size_t ndiff = 0;
for (size_t i = 0; i < codes.size(); i++) {
if (codes[i] != codes2[i]) ndiff++;
}
printf("ndiff for idempotence: %ld / %ld\n", ndiff, codes.size());
std::unique_ptr<ScalarQuantizer::SQDistanceComputer>
dc(sq.get_distance_computer());
dc->codes = codes.data();
dc->code_size = sq.code_size;
printf("code size: %ld\n", dc->code_size);
double sum_dis = 0;
double t0 = getmillisecs();
for (int i = 0; i < n; i++) {
dc->set_query(&x[i * d]);
for (int j = 0; j < n; j++) {
sum_dis += (*dc)(j);
}
}
printf("distances computed in %.3f ms, checksum=%g\n",
getmillisecs() - t0, sum_dis);
return 0;
}
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
import os
import sys
import time
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
from __future__ import print_function
import os
import numpy as np
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python2
"""
Common functions to load datasets and compute their ground-truth
"""
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python2
import os
import numpy as np
from matplotlib import pyplot
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python3
from __future__ import print_function
import numpy as np
import faiss
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python2
from __future__ import print_function
import numpy as np
import time
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
from __future__ import print_function
import os
import time
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
from __future__ import print_function
import time
import sys
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python3
"""small test script to benchmark the SIMD implementation of the
distance computations for the additional metrics. Call eg. with L1 to
get L1 distance computations.
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
from __future__ import print_function
import os
import sys
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
from __future__ import print_function
import time
import numpy as np
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
from __future__ import print_function
import time
import numpy as np
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python2
from __future__ import print_function
import numpy as np
import faiss
-1
View File
@@ -1,4 +1,3 @@
# Distributed on-disk index for 1T-scale datasets
This is code corresponding to the description in [Indexing 1T vectors](https://github.com/facebookresearch/faiss/wiki/Indexing-1T-vectors).
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python3
import os
import faiss
import numpy as np
@@ -1,10 +1,10 @@
#! /usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python3
"""
Simple distributed kmeans implementation Relies on an abstraction
for the training matrix, that can be sharded over several machines.
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python3
"""
Simplistic RPC implementation.
Exposes all functions of a Server object.
+1
View File
@@ -1,4 +1,5 @@
#! /bin/bash
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python2
import numpy as np
import time
import faiss
-1
View File
@@ -1,6 +1,5 @@
README for the link & code implementation
=========================================
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
import os
import sys
import time
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python2
"""
Common functions to load datasets and compute their ground-truth
"""
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python2
"""
This is the training code for the link and code. Especially the
neighbors_kmeans function implements the EM-algorithm to find the
+88
View File
@@ -0,0 +1,88 @@
#! /usr/bin/env python3
from multiprocessing.dummy import Pool as ThreadPool
import faiss
from typing import List, Tuple
from . import rpc
############################################################
# Server implementation
############################################################
class SearchServer(rpc.Server):
""" Assign version that can be exposed via RPC """
def __init__(self, s: int, index: faiss.Index):
rpc.Server.__init__(self, s)
self.index = index
self.index_ivf = faiss.extract_index_ivf(index)
def set_nprobe(self, nprobe: int) -> int:
""" set nprobe field """
self.index_ivf.nprobe = nprobe
def get_ntotal(self) -> int:
return self.index.ntotal
def __getattr__(self, f):
# all other functions get forwarded to the index
return getattr(self.index, f)
def run_index_server(index: faiss.Index, port: int, v6: bool = False):
""" serve requests for that index forerver """
rpc.run_server(
lambda s: SearchServer(s, index),
port, v6=v6)
############################################################
# Client implementation
############################################################
class ClientIndex:
"""manages a set of distance sub-indexes. The sub_indexes search a
subset of the inverted lists. Searches are merged afterwards
"""
def __init__(self, machine_ports: List[Tuple[str, int]], v6: bool = False):
""" connect to a series of (host, port) pairs """
self.sub_indexes = []
for machine, port in machine_ports:
self.sub_indexes.append(rpc.Client(machine, port, v6))
self.ni = len(self.sub_indexes)
# pool of threads. Each thread manages one sub-index.
self.pool = ThreadPool(self.ni)
# test connection...
self.ntotal = self.get_ntotal()
self.verbose = False
def set_nprobe(self, nprobe: int) -> None:
self.pool.map(
lambda idx: idx.set_nprobe(nprobe),
self.sub_indexes
)
def set_omp_num_threads(self, nt: int) -> None:
self.pool.map(
lambda idx: idx.set_omp_num_threads(nt),
self.sub_indexes
)
def get_ntotal(self) -> None:
return sum(self.pool.map(
lambda idx: idx.get_ntotal(),
self.sub_indexes
))
def search(self, x, k: int):
rh = faiss.ResultHeap(x.shape[0], k)
for Di, Ii in self.pool.imap(lambda idx: idx.search(x, k), self.sub_indexes):
rh.add_result(Di, Ii)
rh.finalize()
return rh.D, rh.I
+39
View File
@@ -0,0 +1,39 @@
#! /usr/bin/env python3
import faiss
import time
import logging
LOG = logging.getLogger(__name__)
def knn_ground_truth(xq, db_iterator, k):
"""Computes the exact KNN search results for a dataset that possibly
does not fit in RAM but for whihch we have an iterator that
returns it block by block.
"""
t0 = time.time()
nq, d = xq.shape
rh = faiss.ResultHeap(nq, k)
index = faiss.IndexFlatL2(d)
if faiss.get_num_gpus():
LOG.info('running on %d GPUs' % faiss.get_num_gpus())
index = faiss.index_cpu_to_all_gpus(index)
# compute ground-truth by blocks of bs, and add to heaps
i0 = 0
for xbi in db_iterator:
ni = xbi.shape[0]
index.add(xbi)
D, I = index.search(xq, k)
I += i0
rh.add_result(D, I)
index.reset()
i0 += ni
LOG.info("%d db elements, %.3f s" % (i0, time.time() - t0))
rh.finalize()
LOG.info("GT time: %.3f s (%d vectors)" % (time.time() - t0, i0))
return rh.D, rh.I
+52
View File
@@ -0,0 +1,52 @@
#! /usr/bin/env python3
from typing import List
import faiss
import logging
LOG = logging.getLogger(__name__)
def merge_ondisk(trained_index: faiss.Index,
shard_fnames: List[str],
ivfdata_fname: str) -> None:
""" Add the contents of the indexes stored in shard_fnames into the index
trained_index. The on-disk data is stored in ivfdata_fname """
# merge the images into an on-disk index
# first load the inverted lists
ivfs = []
for fname in shard_fnames:
# the IO_FLAG_MMAP is to avoid actually loading the data thus
# the total size of the inverted lists can exceed the
# available RAM
LOG.info("read " + fname)
index = faiss.read_index(fname, faiss.IO_FLAG_MMAP)
index_ivf = faiss.extract_index_ivf(index)
ivfs.append(index_ivf.invlists)
# avoid that the invlists get deallocated with the index
index_ivf.own_invlists = False
# construct the output index
index = trained_index
index_ivf = faiss.extract_index_ivf(index)
assert index.ntotal == 0, "works only on empty index"
# prepare the output inverted lists. They will be written
# to merged_index.ivfdata
invlists = faiss.OnDiskInvertedLists(
index_ivf.nlist, index_ivf.code_size,
ivfdata_fname)
# merge all the inverted lists
ivf_vector = faiss.InvertedListsPtrVector()
for ivf in ivfs:
ivf_vector.push_back(ivf)
LOG.info("merge %d inverted lists " % ivf_vector.size())
ntotal = invlists.merge_from(ivf_vector.data(), ivf_vector.size())
# now replace the inverted lists in the output index
index.ntotal = index_ivf.ntotal = ntotal
index_ivf.replace_invlists(invlists, True)
invlists.this.disown()
Executable
+248
View File
@@ -0,0 +1,248 @@
#!/usr/bin/env python3
"""
Simplistic RPC implementation.
Exposes all functions of a Server object.
Uses pickle for serialization and the socket interface.
"""
import os,pdb,pickle,time,errno,sys,_thread,traceback,socket,threading,gc
import logging
LOG = logging.getLogger(__name__)
# default
PORT=12032
#########################################################################
# simple I/O functions
def inline_send_handle(f, conn):
st = os.fstat(f.fileno())
size = st.st_size
pickle.dump(size, conn)
conn.write(f.read(size))
def inline_send_string(s, conn):
size = len(s)
pickle.dump(size, conn)
conn.write(s)
class FileSock:
" wraps a socket so that it is usable by pickle/cPickle "
def __init__(self,sock):
self.sock = sock
self.nr=0
def write(self, buf):
# print("sending %d bytes"%len(buf))
#self.sock.sendall(buf)
# print("...done")
bs = 512 * 1024
ns = 0
while ns < len(buf):
sent = self.sock.send(buf[ns:ns + bs])
ns += sent
def read(self,bs=512*1024):
#if self.nr==10000: pdb.set_trace()
self.nr+=1
# print("read bs=%d"%bs)
b = []
nb = 0
while len(b)<bs:
# print(' loop')
rb = self.sock.recv(bs - nb)
if not rb: break
b.append(rb)
nb += len(rb)
return b''.join(b)
def readline(self):
# print("readline!")
"""may be optimized..."""
s=bytes()
while True:
c=self.read(1)
s+=c
if len(c)==0 or chr(c[0])=='\n':
return s
class ClientExit(Exception):
pass
class ServerException(Exception):
pass
class Server:
"""
server protocol. Methods from classes that subclass Server can be called
transparently from a client
"""
def __init__(self, s, logf=sys.stderr, log_prefix=''):
self.logf = logf
self.log_prefix = log_prefix
# connection
self.conn = s
self.fs = FileSock(s)
def log(self, s):
self.logf.write("Sever log %s: %s\n" % (self.log_prefix, s))
def one_function(self):
"""
Executes a single function with associated I/O.
Protocol:
- the arguments and results are serialized with the pickle protocol
- client sends : (fname,args)
fname = method name to call
args = tuple of arguments
- server sends result: (rid,st,ret)
rid = request id
st = None, or exception if there was during execution
ret = return value or None if st!=None
"""
try:
(fname,args)=pickle.load(self.fs)
except EOFError:
raise ClientExit("read args")
self.log("executing method %s"%(fname))
st = None
ret = None
try:
f=getattr(self,fname)
except AttributeError:
st = AttributeError("unknown method "+fname)
self.log("unknown method ")
try:
ret = f(*args)
except Exception as e:
# due to a bug (in mod_python?), ServerException cannot be
# unpickled, so send the string and make the exception on the client side
#st=ServerException(
# "".join(traceback.format_tb(sys.exc_info()[2]))+
# str(e))
st="".join(traceback.format_tb(sys.exc_info()[2]))+str(e)
self.log("exception in method")
traceback.print_exc(50,self.logf)
self.logf.flush()
LOG.info("return")
try:
pickle.dump((st ,ret), self.fs, protocol=4)
except EOFError:
raise ClientExit("function return")
def exec_loop(self):
""" main execution loop. Loops and handles exit states"""
self.log("in exec_loop")
try:
while True:
self.one_function()
except ClientExit as e:
self.log("ClientExit %s"%e)
except socket.error as e:
self.log("socket error %s"%e)
traceback.print_exc(50,self.logf)
except EOFError:
self.log("EOF during communication")
traceback.print_exc(50,self.logf)
except BaseException:
# unexpected
traceback.print_exc(50,sys.stderr)
sys.exit(1)
LOG.info("exit sever")
def exec_loop_cleanup(self):
pass
###################################################################
# spying stuff
def get_ps_stats(self):
ret=''
f=os.popen("echo ============ `hostname` uptime:; uptime;"+
"echo ============ self:; "+
"ps -p %d -o pid,vsize,rss,%%cpu,nlwp,psr; "%os.getpid()+
"echo ============ run queue:;"+
"ps ar -o user,pid,%cpu,%mem,ni,nlwp,psr,vsz,rss,cputime,command")
for l in f:
ret+=l
return ret
class Client:
"""
Methods of the server object can be called transparently. Exceptions are
re-raised.
"""
def __init__(self, HOST, port=PORT, v6=False):
socktype = socket.AF_INET6 if v6 else socket.AF_INET
sock = socket.socket(socktype, socket.SOCK_STREAM)
LOG.info("connecting", HOST, port, socktype)
sock.connect((HOST, port))
self.sock = sock
self.fs = FileSock(sock)
def generic_fun(self, fname, args):
# int "gen fun",fname
pickle.dump((fname, args), self.fs, protocol=4)
return self.get_result()
def get_result(self):
(st, ret) = pickle.load(self.fs)
if st!=None:
raise ServerException(st)
else:
return ret
def __getattr__(self,name):
return lambda *x: self.generic_fun(name,x)
def run_server(new_handler, port=PORT, report_to_file=None, v6=False):
HOST = '' # Symbolic name meaning the local host
socktype = socket.AF_INET6 if v6 else socket.AF_INET
s = socket.socket(socktype, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
LOG.info("bind %s:%d" % (HOST, port))
s.bind((HOST, port))
s.listen(5)
LOG.info("accepting connections")
if report_to_file is not None:
LOG.info('storing host+port in', report_to_file)
open(report_to_file, 'w').write('%s:%d ' % (socket.gethostname(), port))
while True:
try:
conn, addr = s.accept()
except socket.error as e:
if e[1]=='Interrupted system call': continue
raise
LOG.info('Connected by', addr, end=' ')
ibs = new_handler(conn)
tid = _thread.start_new_thread(ibs.exec_loop,())
LOG.info("tid",tid)
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python2
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
from __future__ import print_function
import os
import time
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
import sys
import numpy as np
import faiss
from faiss_contrib.client_server import run_index_server, ClientIndex
#################################################################
# Small I/O functions
#################################################################
def ivecs_read(fname):
a = np.fromfile(fname, dtype='int32')
d = a[0]
return a.reshape(-1, d + 1)[:, 1:].copy()
def fvecs_read(fname):
return ivecs_read(fname).view('float32')
#################################################################
# Main program
#################################################################
stage = int(sys.argv[1])
tmpdir = '/tmp/'
if stage == 0:
# train the index
xt = fvecs_read("sift1M/sift_learn.fvecs")
index = faiss.index_factory(xt.shape[1], "IVF4096,Flat")
print("training index")
index.train(xt)
print("write " + tmpdir + "trained.index")
faiss.write_index(index, tmpdir + "trained.index")
if 1 <= stage <= 4:
# add 1/4 of the database to 4 independent indexes
bno = stage - 1
xb = fvecs_read("sift1M/sift_base.fvecs")
i0, i1 = int(bno * xb.shape[0] / 4), int((bno + 1) * xb.shape[0] / 4)
index = faiss.read_index(tmpdir + "trained.index")
print("adding vectors %d:%d" % (i0, i1))
index.add_with_ids(xb[i0:i1], np.arange(i0, i1))
print("write " + tmpdir + "block_%d.index" % bno)
faiss.write_index(index, tmpdir + "block_%d.index" % bno)
machine_ports = [
('localhost', 12010),
('localhost', 12011),
('localhost', 12012),
('localhost', 12013),
]
v6 = False
if 5 <= stage <= 8:
# load an index slice and launch index
bno = stage - 5
fname = tmpdir + "block_%d.index" % bno
print("read " + fname)
index = faiss.read_index(fname)
port = machine_ports[bno][1]
run_index_server(index, port, v6=v6)
if stage == 9:
client_index = ClientIndex(machine_ports)
print('index size:', client_index.ntotal)
client_index.set_nprobe(16)
# load query vectors and ground-truth
xq = fvecs_read("sift1M/sift_query.fvecs")
gt = ivecs_read("sift1M/sift_groundtruth.ivecs")
D, I = client_index.search(xq, 5)
recall_at_1 = (I[:, :1] == gt[:, :1]).sum() / float(xq.shape[0])
print("recall@1: %.3f" % recall_at_1)
+10 -35
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#!/usr/bin/env python2
import sys
import numpy as np
import faiss
from faiss_contrib.ondisk import merge_ondisk
#################################################################
# Small I/O functions
@@ -26,7 +26,7 @@ def fvecs_read(fname):
#################################################################
# Main program
# Main program
#################################################################
stage = int(sys.argv[1])
@@ -54,43 +54,18 @@ if 1 <= stage <= 4:
print("write " + tmpdir + "block_%d.index" % bno)
faiss.write_index(index, tmpdir + "block_%d.index" % bno)
if stage == 5:
# merge the images into an on-disk index
# first load the inverted lists
ivfs = []
for bno in range(4):
# the IO_FLAG_MMAP is to avoid actually loading the data thus
# the total size of the inverted lists can exceed the
# available RAM
print("read " + tmpdir + "block_%d.index" % bno)
index = faiss.read_index(tmpdir + "block_%d.index" % bno,
faiss.IO_FLAG_MMAP)
ivfs.append(index.invlists)
# avoid that the invlists get deallocated with the index
index.own_invlists = False
print('loading trained index')
# construct the output index
index = faiss.read_index(tmpdir + "trained.index")
# prepare the output inverted lists. They will be written
# to merged_index.ivfdata
invlists = faiss.OnDiskInvertedLists(
index.nlist, index.code_size,
tmpdir + "merged_index.ivfdata")
block_fnames = [
tmpdir + "block_%d.index" % bno
for bno in range(4)
]
# merge all the inverted lists
ivf_vector = faiss.InvertedListsPtrVector()
for ivf in ivfs:
ivf_vector.push_back(ivf)
print("merge %d inverted lists " % ivf_vector.size())
ntotal = invlists.merge_from(ivf_vector.data(), ivf_vector.size())
# now replace the inverted lists in the output index
index.ntotal = ntotal
index.replace_invlists(invlists)
merge_ondisk(index, block_fnames, tmpdir + "merged_index.ivfdata")
print("write " + tmpdir + "populated.index")
faiss.write_index(index, tmpdir + "populated.index")
+387 -1339
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -114,9 +114,9 @@ faiss::Index * index_gpu_to_cpu(const faiss::Index *gpu_index)
* Cloning to 1 GPU
**********************************************************/
ToGpuCloner::ToGpuCloner(GpuResources *resources, int device,
ToGpuCloner::ToGpuCloner(GpuResourcesProvider *prov, int device,
const GpuClonerOptions &options):
GpuClonerOptions(options), resources(resources), device(device)
GpuClonerOptions(options), provider(prov), device(device)
{}
Index *ToGpuCloner::clone_Index(const Index *index)
@@ -127,7 +127,7 @@ Index *ToGpuCloner::clone_Index(const Index *index)
config.useFloat16 = useFloat16;
config.storeTransposed = storeTransposed;
return new GpuIndexFlat(resources, ifl, config);
return new GpuIndexFlat(provider, ifl, config);
} else if(auto ifl = dynamic_cast<const faiss::IndexIVFFlat *>(index)) {
GpuIndexIVFFlatConfig config;
config.device = device;
@@ -136,7 +136,7 @@ Index *ToGpuCloner::clone_Index(const Index *index)
config.flatConfig.storeTransposed = storeTransposed;
GpuIndexIVFFlat *res =
new GpuIndexIVFFlat(resources,
new GpuIndexIVFFlat(provider,
ifl->d,
ifl->nlist,
ifl->metric_type,
@@ -156,7 +156,7 @@ Index *ToGpuCloner::clone_Index(const Index *index)
config.flatConfig.storeTransposed = storeTransposed;
GpuIndexIVFScalarQuantizer *res =
new GpuIndexIVFScalarQuantizer(resources,
new GpuIndexIVFScalarQuantizer(provider,
ifl->d,
ifl->nlist,
ifl->sq.qtype,
@@ -184,7 +184,7 @@ Index *ToGpuCloner::clone_Index(const Index *index)
config.useFloat16LookupTables = useFloat16;
config.usePrecomputedTables = usePrecomputed;
GpuIndexIVFPQ *res = new GpuIndexIVFPQ(resources, ipq, config);
GpuIndexIVFPQ *res = new GpuIndexIVFPQ(provider, ipq, config);
if(reserveVecs > 0 && ipq->ntotal == 0) {
res->reserveMemory(reserveVecs);
@@ -198,12 +198,12 @@ Index *ToGpuCloner::clone_Index(const Index *index)
faiss::Index * index_cpu_to_gpu(
GpuResources* resources, int device,
GpuResourcesProvider* provider, int device,
const faiss::Index *index,
const GpuClonerOptions *options)
{
GpuClonerOptions defaults;
ToGpuCloner cl(resources, device, options ? *options : defaults);
ToGpuCloner cl(provider, device, options ? *options : defaults);
return cl.clone_Index(index);
}
@@ -213,14 +213,14 @@ faiss::Index * index_cpu_to_gpu(
**********************************************************/
ToGpuClonerMultiple::ToGpuClonerMultiple(
std::vector<GpuResources *> & resources,
std::vector<GpuResourcesProvider *> & provider,
std::vector<int>& devices,
const GpuMultipleClonerOptions &options):
GpuMultipleClonerOptions(options)
{
FAISS_ASSERT(resources.size() == devices.size());
for(int i = 0; i < resources.size(); i++) {
sub_cloners.push_back(ToGpuCloner(resources[i], devices[i], options));
FAISS_ASSERT(provider.size() == devices.size());
for(int i = 0; i < provider.size(); i++) {
sub_cloners.push_back(ToGpuCloner(provider[i], devices[i], options));
}
}
@@ -394,13 +394,13 @@ Index *ToGpuClonerMultiple::clone_Index(const Index *index)
faiss::Index * index_cpu_to_gpu_multiple(
std::vector<GpuResources*> & resources,
std::vector<GpuResourcesProvider*> & provider,
std::vector<int> &devices,
const faiss::Index *index,
const GpuMultipleClonerOptions *options)
{
GpuMultipleClonerOptions defaults;
ToGpuClonerMultiple cl(resources, devices, options ? *options : defaults);
ToGpuClonerMultiple cl(provider, devices, options ? *options : defaults);
return cl.clone_Index(index);
}
+6 -7
View File
@@ -17,8 +17,7 @@
namespace faiss { namespace gpu {
class GpuResources;
class GpuResourcesProvider;
/// Cloner specialized for GPU -> CPU
struct ToCPUCloner: faiss::Cloner {
@@ -29,10 +28,10 @@ struct ToCPUCloner: faiss::Cloner {
/// Cloner specialized for CPU -> 1 GPU
struct ToGpuCloner: faiss::Cloner, GpuClonerOptions {
GpuResources *resources;
GpuResourcesProvider *provider;
int device;
ToGpuCloner(GpuResources *resources, int device,
ToGpuCloner(GpuResourcesProvider *prov, int device,
const GpuClonerOptions &options);
Index *clone_Index(const Index *index) override;
@@ -43,7 +42,7 @@ struct ToGpuCloner: faiss::Cloner, GpuClonerOptions {
struct ToGpuClonerMultiple: faiss::Cloner, GpuMultipleClonerOptions {
std::vector<ToGpuCloner> sub_cloners;
ToGpuClonerMultiple(std::vector<GpuResources *> & resources,
ToGpuClonerMultiple(std::vector<GpuResourcesProvider *> & provider,
std::vector<int>& devices,
const GpuMultipleClonerOptions &options);
@@ -67,12 +66,12 @@ faiss::Index * index_gpu_to_cpu(const faiss::Index *gpu_index);
/// converts any CPU index that can be converted to GPU
faiss::Index * index_cpu_to_gpu(
GpuResources* resources, int device,
GpuResourcesProvider* provider, int device,
const faiss::Index *index,
const GpuClonerOptions *options = nullptr);
faiss::Index * index_cpu_to_gpu_multiple(
std::vector<GpuResources*> & resources,
std::vector<GpuResourcesProvider*> & provider,
std::vector<int> &devices,
const faiss::Index *index,
const GpuMultipleClonerOptions *options = nullptr);
+44 -37
View File
@@ -18,49 +18,56 @@
namespace faiss { namespace gpu {
template <typename T>
void bfKnnConvert(GpuResources* resources, const GpuDistanceParams& args) {
void bfKnnConvert(GpuResourcesProvider* prov, const GpuDistanceParams& args) {
// Don't let the resources go out of scope
auto resImpl = prov->getResources();
auto res = resImpl.get();
auto device = getCurrentDevice();
auto stream = resources->getDefaultStreamCurrentDevice();
auto& mem = resources->getMemoryManagerCurrentDevice();
auto stream = res->getDefaultStreamCurrentDevice();
auto tVectors =
toDevice<T, 2>(resources,
device,
const_cast<T*>(reinterpret_cast<const T*>(args.vectors)),
stream,
{args.vectorsRowMajor ? args.numVectors : args.dims,
args.vectorsRowMajor ? args.dims : args.numVectors});
toDeviceTemporary<T, 2>(
res,
device,
const_cast<T*>(reinterpret_cast<const T*>(args.vectors)),
stream,
{args.vectorsRowMajor ? args.numVectors : args.dims,
args.vectorsRowMajor ? args.dims : args.numVectors});
auto tQueries =
toDevice<T, 2>(resources,
device,
const_cast<T*>(reinterpret_cast<const T*>(args.queries)),
stream,
{args.queriesRowMajor ? args.numQueries : args.dims,
args.queriesRowMajor ? args.dims : args.numQueries});
toDeviceTemporary<T, 2>(
res,
device,
const_cast<T*>(reinterpret_cast<const T*>(args.queries)),
stream,
{args.queriesRowMajor ? args.numQueries : args.dims,
args.queriesRowMajor ? args.dims : args.numQueries});
DeviceTensor<float, 1, true> tVectorNorms;
if (args.vectorNorms) {
tVectorNorms = toDevice<float, 1>(resources,
device,
const_cast<float*>(args.vectorNorms),
stream,
{args.numVectors});
tVectorNorms =
toDeviceTemporary<float, 1>(res,
device,
const_cast<float*>(args.vectorNorms),
stream,
{args.numVectors});
}
auto tOutDistances =
toDevice<float, 2>(resources,
device,
args.outDistances,
stream,
{args.numQueries, args.k});
toDeviceTemporary<float, 2>(res,
device,
args.outDistances,
stream,
{args.numQueries, args.k});
// The brute-force API only supports an interface for integer indices
DeviceTensor<int, 2, true>
tOutIntIndices(mem, {args.numQueries, args.k}, stream);
tOutIntIndices(res,
makeTempAlloc(AllocType::Other, stream),
{args.numQueries, args.k});
// Since we've guaranteed that all arguments are on device, call the
// implementation
bfKnnOnDevice<T>(resources,
bfKnnOnDevice<T>(res,
device,
stream,
tVectors,
@@ -77,11 +84,11 @@ void bfKnnConvert(GpuResources* resources, const GpuDistanceParams& args) {
// Convert and copy int indices out
auto tOutIndices =
toDevice<faiss::Index::idx_t, 2>(resources,
device,
args.outIndices,
stream,
{args.numQueries, args.k});
toDeviceTemporary<faiss::Index::idx_t, 2>(res,
device,
args.outIndices,
stream,
{args.numQueries, args.k});
// Convert int to idx_t
convertTensor<int, faiss::Index::idx_t, 2>(stream,
@@ -94,7 +101,7 @@ void bfKnnConvert(GpuResources* resources, const GpuDistanceParams& args) {
}
void
bfKnn(GpuResources* resources, const GpuDistanceParams& args) {
bfKnn(GpuResourcesProvider* res, const GpuDistanceParams& args) {
// For now, both vectors and queries must be of the same data type
FAISS_THROW_IF_NOT_MSG(
args.vectorType == args.queryType,
@@ -102,9 +109,9 @@ bfKnn(GpuResources* resources, const GpuDistanceParams& args) {
"be the same (F32 or F16");
if (args.vectorType == DistanceDataType::F32) {
bfKnnConvert<float>(resources, args);
bfKnnConvert<float>(res, args);
} else if (args.vectorType == DistanceDataType::F16) {
bfKnnConvert<half>(resources, args);
bfKnnConvert<half>(res, args);
} else {
FAISS_THROW_MSG("unknown vectorType");
}
@@ -112,7 +119,7 @@ bfKnn(GpuResources* resources, const GpuDistanceParams& args) {
// legacy version
void
bruteForceKnn(GpuResources* resources,
bruteForceKnn(GpuResourcesProvider* res,
faiss::MetricType metric,
// A region of memory size numVectors x dims, with dims
// innermost
@@ -147,7 +154,7 @@ bruteForceKnn(GpuResources* resources,
args.outDistances = outDistances;
args.outIndices = outIndices;
bfKnn(resources, args);
bfKnn(res, args);
}
} } // namespace
+3 -3
View File
@@ -12,7 +12,7 @@
namespace faiss { namespace gpu {
class GpuResources;
class GpuResourcesProvider;
// Scalar type of the vector data
enum class DistanceDataType {
@@ -116,10 +116,10 @@ struct GpuDistanceParams {
///
/// For each vector in `queries`, searches all of `vectors` to find its k
/// nearest neighbors with respect to the given metric
void bfKnn(GpuResources* resources, const GpuDistanceParams& args);
void bfKnn(GpuResourcesProvider* resources, const GpuDistanceParams& args);
/// Deprecated legacy implementation
void bruteForceKnn(GpuResources* resources,
void bruteForceKnn(GpuResourcesProvider* resources,
faiss::MetricType metric,
// If vectorsRowMajor is true, this is
// numVectors x dims, with dims innermost; otherwise,
+33 -36
View File
@@ -35,7 +35,7 @@ constexpr size_t kAddVecSize = (size_t) 512 * 1024;
// FIXME: parameterize based on algorithm need
constexpr size_t kSearchVecSize = (size_t) 32 * 1024;
GpuIndex::GpuIndex(GpuResources* resources,
GpuIndex::GpuIndex(std::shared_ptr<GpuResources> resources,
int dims,
faiss::MetricType metric,
float metricArg,
@@ -50,21 +50,16 @@ GpuIndex::GpuIndex(GpuResources* resources,
FAISS_THROW_IF_NOT_MSG(dims > 0, "Invalid number of dimensions");
#ifdef FAISS_UNIFIED_MEM
FAISS_THROW_IF_NOT_FMT(
memorySpace_ == MemorySpace::Device ||
(memorySpace_ == MemorySpace::Unified &&
getFullUnifiedMemSupport(device_)),
"Device %d does not support full CUDA 8 Unified Memory (CC 6.0+)",
config.device);
#else
FAISS_THROW_IF_NOT_MSG(memorySpace_ == MemorySpace::Device,
"Must compile with CUDA 8+ for Unified Memory support");
#endif
metric_arg = metricArg;
FAISS_ASSERT(resources_);
FAISS_ASSERT((bool) resources_);
resources_->initializeForDevice(device_);
}
@@ -174,18 +169,20 @@ GpuIndex::addPage_(int n,
// Before continuing, we guarantee that all data will be resident on the GPU.
auto stream = resources_->getDefaultStreamCurrentDevice();
auto vecs = toDevice<float, 2>(resources_,
device_,
const_cast<float*>(x),
stream,
{n, this->d});
auto vecs =
toDeviceTemporary<float, 2>(resources_.get(),
device_,
const_cast<float*>(x),
stream,
{n, this->d});
if (ids) {
auto indices = toDevice<Index::idx_t, 1>(resources_,
device_,
const_cast<Index::idx_t*>(ids),
stream,
{n});
auto indices =
toDeviceTemporary<Index::idx_t, 1>(resources_.get(),
device_,
const_cast<Index::idx_t*>(ids),
stream,
{n});
addImpl_(n, vecs.data(), ids ? indices.data() : nullptr);
} else {
@@ -230,12 +227,14 @@ GpuIndex::search(Index::idx_t n,
// If we reach a point where all inputs are too big, we can add
// another level of tiling.
auto outDistances =
toDevice<float, 2>(resources_, device_, distances, stream,
{(int) n, (int) k});
toDeviceTemporary<float, 2>(
resources_.get(), device_, distances, stream,
{(int) n, (int) k});
auto outLabels =
toDevice<faiss::Index::idx_t, 2>(resources_, device_, labels, stream,
{(int) n, (int) k});
toDeviceTemporary<faiss::Index::idx_t, 2>(
resources_.get(), device_, labels, stream,
{(int) n, (int) k});
bool usePaged = false;
@@ -277,11 +276,11 @@ GpuIndex::searchNonPaged_(int n,
// Make sure arguments are on the device we desire; use temporary
// memory allocations to move it if necessary
auto vecs = toDevice<float, 2>(resources_,
device_,
const_cast<float*>(x),
stream,
{n, (int) this->d});
auto vecs = toDeviceTemporary<float, 2>(resources_.get(),
device_,
const_cast<float*>(x),
stream,
{n, (int) this->d});
searchImpl_(n, vecs.data(), k, outDistancesData, outIndicesData);
}
@@ -349,13 +348,13 @@ GpuIndex::searchFromCpuPaged_(int n,
// Reserve space on the GPU for the destination of the pinned buffer
// copy
DeviceTensor<float, 2, true> bufGpuA(
resources_->getMemoryManagerCurrentDevice(),
{(int) pageSizeInVecs, (int) this->d},
defaultStream);
resources_.get(),
makeTempAlloc(AllocType::Other, defaultStream),
{(int) pageSizeInVecs, (int) this->d});
DeviceTensor<float, 2, true> bufGpuB(
resources_->getMemoryManagerCurrentDevice(),
{(int) pageSizeInVecs, (int) this->d},
defaultStream);
resources_.get(),
makeTempAlloc(AllocType::Other, defaultStream),
{(int) pageSizeInVecs, (int) this->d});
DeviceTensor<float, 2, true>* bufGpus[2] = {&bufGpuA, &bufGpuB};
// Copy completion events for the pinned buffers
@@ -398,8 +397,7 @@ GpuIndex::searchFromCpuPaged_(int n,
copyStream));
// Mark a completion event in this stream
eventPinnedCopyDone[cur2BufIndex] =
std::move(std::unique_ptr<CudaEvent>(new CudaEvent(copyStream)));
eventPinnedCopyDone[cur2BufIndex].reset(new CudaEvent(copyStream));
// We pick up from here
cur3 = cur2;
@@ -430,8 +428,7 @@ GpuIndex::searchFromCpuPaged_(int n,
outIndicesSlice.data());
// Create completion event
eventGpuExecuteDone[cur3BufIndex] =
std::move(std::unique_ptr<CudaEvent>(new CudaEvent(defaultStream)));
eventGpuExecuteDone[cur3BufIndex].reset(new CudaEvent(defaultStream));
// We pick up from here
cur3BufIndex = (cur3BufIndex == 0) ? 1 : 0;
+3 -9
View File
@@ -9,12 +9,10 @@
#pragma once
#include <faiss/Index.h>
#include <faiss/gpu/utils/MemorySpace.h>
#include <faiss/gpu/GpuResources.h>
namespace faiss { namespace gpu {
class GpuResources;
struct GpuIndexConfig {
inline GpuIndexConfig()
: device(0),
@@ -32,7 +30,7 @@ struct GpuIndexConfig {
class GpuIndex : public faiss::Index {
public:
GpuIndex(GpuResources* resources,
GpuIndex(std::shared_ptr<GpuResources> resources,
int dims,
faiss::MetricType metric,
float metricArg,
@@ -42,10 +40,6 @@ class GpuIndex : public faiss::Index {
return device_;
}
inline GpuResources* getResources() {
return resources_;
}
/// Set the minimum data size for searches (in MiB) for which we use
/// CPU -> GPU paging
void setMinPagingSize(size_t size);
@@ -140,7 +134,7 @@ private:
protected:
/// Manages streams, cuBLAS handles and scratch memory for devices
GpuResources* resources_;
std::shared_ptr<GpuResources> resources_;
/// The GPU device we are resident on
const int device_;
+30 -33
View File
@@ -18,13 +18,12 @@ namespace faiss { namespace gpu {
/// Default CPU search size for which we use paged copies
constexpr size_t kMinPageSize = (size_t) 256 * 1024 * 1024;
GpuIndexBinaryFlat::GpuIndexBinaryFlat(GpuResources* resources,
GpuIndexBinaryFlat::GpuIndexBinaryFlat(GpuResourcesProvider* provider,
const faiss::IndexBinaryFlat* index,
GpuIndexBinaryFlatConfig config)
: IndexBinary(index->d),
resources_(resources),
config_(std::move(config)),
data_(nullptr) {
resources_(provider->getResources()),
config_(std::move(config)) {
FAISS_THROW_IF_NOT_FMT(this->d % 8 == 0,
"vector dimension (number of bits) "
"must be divisible by 8 (passed %d)",
@@ -37,13 +36,12 @@ GpuIndexBinaryFlat::GpuIndexBinaryFlat(GpuResources* resources,
}
GpuIndexBinaryFlat::GpuIndexBinaryFlat(GpuResources* resources,
GpuIndexBinaryFlat::GpuIndexBinaryFlat(GpuResourcesProvider* provider,
int dims,
GpuIndexBinaryFlatConfig config)
: IndexBinary(dims),
resources_(resources),
config_(std::move(config)),
data_(nullptr) {
resources_(provider->getResources()),
config_(std::move(config)) {
FAISS_THROW_IF_NOT_FMT(this->d % 8 == 0,
"vector dimension (number of bits) "
"must be divisible by 8 (passed %d)",
@@ -54,13 +52,11 @@ GpuIndexBinaryFlat::GpuIndexBinaryFlat(GpuResources* resources,
// Construct index
DeviceScope scope(config_.device);
data_ = new BinaryFlatIndex(resources,
this->d,
config_.memorySpace);
data_.reset(
new BinaryFlatIndex(resources_.get(), this->d, config_.memorySpace));
}
GpuIndexBinaryFlat::~GpuIndexBinaryFlat() {
delete data_;
}
void
@@ -78,10 +74,10 @@ GpuIndexBinaryFlat::copyFrom(const faiss::IndexBinaryFlat* index) {
(size_t) index->ntotal);
this->ntotal = index->ntotal;
delete data_;
data_ = new BinaryFlatIndex(resources_,
this->d,
config_.memorySpace);
// destroy old first before allocating new
data_.reset();
data_.reset(
new BinaryFlatIndex(resources_.get(), this->d, config_.memorySpace));
// The index could be empty
if (index->ntotal > 0) {
@@ -168,16 +164,16 @@ GpuIndexBinaryFlat::search(faiss::IndexBinary::idx_t n,
// GPU.
// If we reach a point where all inputs are too big, we can add
// another level of tiling.
auto outDistances = toDevice<int32_t, 2>(resources_,
config_.device,
distances,
stream,
{(int) n, (int) k});
auto outDistances = toDeviceTemporary<int32_t, 2>(resources_.get(),
config_.device,
distances,
stream,
{(int) n, (int) k});
// FlatIndex only supports an interface returning int indices
DeviceTensor<int, 2, true> outIntIndices(
resources_->getMemoryManagerCurrentDevice(),
{(int) n, (int) k}, stream);
resources_.get(), makeTempAlloc(AllocType::Other, stream),
{(int) n, (int) k});
bool usePaged = false;
@@ -205,11 +201,12 @@ GpuIndexBinaryFlat::search(faiss::IndexBinary::idx_t n,
}
// Convert and copy int indices out
auto outIndices = toDevice<faiss::Index::idx_t, 2>(resources_,
config_.device,
labels,
stream,
{(int) n, (int) k});
auto outIndices =
toDeviceTemporary<faiss::Index::idx_t, 2>(resources_.get(),
config_.device,
labels,
stream,
{(int) n, (int) k});
// Convert int to long
convertTensor<int, faiss::Index::idx_t, 2>(stream,
@@ -234,11 +231,11 @@ GpuIndexBinaryFlat::searchNonPaged_(int n,
// Make sure arguments are on the device we desire; use temporary
// memory allocations to move it if necessary
auto vecs = toDevice<uint8_t, 2>(resources_,
config_.device,
const_cast<uint8_t*>(x),
stream,
{n, (int) (this->d / 8)});
auto vecs = toDeviceTemporary<uint8_t, 2>(resources_.get(),
config_.device,
const_cast<uint8_t*>(x),
stream,
{n, (int) (this->d / 8)});
data_->query(vecs, k, outDistances, outIndices);
}
+7 -7
View File
@@ -9,11 +9,12 @@
#include <faiss/IndexBinaryFlat.h>
#include <faiss/gpu/GpuIndex.h>
#include <faiss/gpu/GpuResources.h>
#include <memory>
namespace faiss { namespace gpu {
class BinaryFlatIndex;
class GpuResources;
struct GpuIndexBinaryFlatConfig : public GpuIndexConfig {
};
@@ -24,13 +25,13 @@ class GpuIndexBinaryFlat : public IndexBinary {
public:
/// Construct from a pre-existing faiss::IndexBinaryFlat instance, copying
/// data over to the given GPU
GpuIndexBinaryFlat(GpuResources* resources,
GpuIndexBinaryFlat(GpuResourcesProvider* resources,
const faiss::IndexBinaryFlat* index,
GpuIndexBinaryFlatConfig config =
GpuIndexBinaryFlatConfig());
/// Construct an empty instance that can be added to
GpuIndexBinaryFlat(GpuResources* resources,
GpuIndexBinaryFlat(GpuResourcesProvider* resources,
int dims,
GpuIndexBinaryFlatConfig config =
GpuIndexBinaryFlatConfig());
@@ -76,14 +77,13 @@ class GpuIndexBinaryFlat : public IndexBinary {
protected:
/// Manages streans, cuBLAS handles and scratch memory for devices
GpuResources* resources_;
std::shared_ptr<GpuResources> resources_;
/// Configuration options
GpuIndexBinaryFlatConfig config_;
/// Holds our GPU data containing the list of vectors; is managed via raw
/// pointer so as to allow non-CUDA compilers to see this header
BinaryFlatIndex* data_;
/// Holds our GPU data containing the list of vectors
std::unique_ptr<BinaryFlatIndex> data_;
};
} } // namespace gpu
+97 -34
View File
@@ -19,7 +19,22 @@
namespace faiss { namespace gpu {
GpuIndexFlat::GpuIndexFlat(GpuResources* resources,
GpuIndexFlat::GpuIndexFlat(GpuResourcesProvider* provider,
const faiss::IndexFlat* index,
GpuIndexFlatConfig config) :
GpuIndex(provider->getResources(),
index->d,
index->metric_type,
index->metric_arg,
config),
config_(std::move(config)) {
// Flat index doesn't need training
this->is_trained = true;
copyFrom(index);
}
GpuIndexFlat::GpuIndexFlat(std::shared_ptr<GpuResources> resources,
const faiss::IndexFlat* index,
GpuIndexFlatConfig config) :
GpuIndex(resources,
@@ -27,35 +42,54 @@ GpuIndexFlat::GpuIndexFlat(GpuResources* resources,
index->metric_type,
index->metric_arg,
config),
config_(std::move(config)),
data_(nullptr) {
config_(std::move(config)) {
// Flat index doesn't need training
this->is_trained = true;
copyFrom(index);
}
GpuIndexFlat::GpuIndexFlat(GpuResources* resources,
GpuIndexFlat::GpuIndexFlat(GpuResourcesProvider* provider,
int dims,
faiss::MetricType metric,
GpuIndexFlatConfig config) :
GpuIndex(resources, dims, metric, 0, config),
config_(std::move(config)),
data_(nullptr) {
GpuIndex(provider->getResources(),
dims,
metric,
0,
config),
config_(std::move(config)) {
// Flat index doesn't need training
this->is_trained = true;
// Construct index
DeviceScope scope(device_);
data_ = new FlatIndex(resources,
dims,
config_.useFloat16,
config_.storeTransposed,
memorySpace_);
data_.reset(new FlatIndex(resources_.get(),
dims,
config_.useFloat16,
config_.storeTransposed,
memorySpace_));
}
GpuIndexFlat::GpuIndexFlat(std::shared_ptr<GpuResources> resources,
int dims,
faiss::MetricType metric,
GpuIndexFlatConfig config) :
GpuIndex(resources, dims, metric, 0, config),
config_(std::move(config)) {
// Flat index doesn't need training
this->is_trained = true;
// Construct index
DeviceScope scope(device_);
data_.reset(new FlatIndex(resources_.get(),
dims,
config_.useFloat16,
config_.storeTransposed,
memorySpace_));
}
GpuIndexFlat::~GpuIndexFlat() {
delete data_;
}
void
@@ -72,12 +106,12 @@ GpuIndexFlat::copyFrom(const faiss::IndexFlat* index) {
(size_t) std::numeric_limits<int>::max(),
(size_t) index->ntotal);
delete data_;
data_ = new FlatIndex(resources_,
this->d,
config_.useFloat16,
config_.storeTransposed,
memorySpace_);
data_.reset();
data_.reset(new FlatIndex(resources_.get(),
this->d,
config_.useFloat16,
config_.storeTransposed,
memorySpace_));
// The index could be empty
if (index->ntotal > 0) {
@@ -199,7 +233,9 @@ GpuIndexFlat::searchImpl_(int n,
// FlatIndex only supports int indices
DeviceTensor<int, 2, true> outIntLabels(
resources_->getMemoryManagerCurrentDevice(), {n, k}, stream);
resources_.get(),
makeTempAlloc(AllocType::Other, stream),
{n, k});
data_->query(queries, k, metric_type, metric_arg,
outDistances, outIntLabels, true);
@@ -270,21 +306,24 @@ GpuIndexFlat::compute_residual_n(faiss::Index::idx_t n,
DeviceScope scope(device_);
auto vecsDevice =
toDevice<float, 2>(resources_, device_,
const_cast<float*>(xs), stream,
{(int) n, (int) this->d});
toDeviceTemporary<float, 2>(resources_.get(), device_,
const_cast<float*>(xs), stream,
{(int) n, (int) this->d});
auto idsDevice =
toDevice<faiss::Index::idx_t, 1>(resources_, device_,
const_cast<faiss::Index::idx_t*>(keys),
stream,
{(int) n});
toDeviceTemporary<faiss::Index::idx_t, 1>(
resources_.get(), device_,
const_cast<faiss::Index::idx_t*>(keys),
stream,
{(int) n});
auto residualDevice =
toDevice<float, 2>(resources_, device_, residuals, stream,
{(int) n, (int) this->d});
toDeviceTemporary<float, 2>(resources_.get(), device_, residuals, stream,
{(int) n, (int) this->d});
// Convert idx_t to int
auto keysInt =
convertTensor<faiss::Index::idx_t, int, 1>(resources_, stream, idsDevice);
convertTensorTemporary<faiss::Index::idx_t, int, 1>(
resources_.get(), stream, idsDevice);
FAISS_ASSERT(data_);
data_->computeResidual(vecsDevice,
@@ -298,13 +337,25 @@ GpuIndexFlat::compute_residual_n(faiss::Index::idx_t n,
// GpuIndexFlatL2
//
GpuIndexFlatL2::GpuIndexFlatL2(GpuResources* resources,
GpuIndexFlatL2::GpuIndexFlatL2(GpuResourcesProvider* provider,
faiss::IndexFlatL2* index,
GpuIndexFlatConfig config) :
GpuIndexFlat(provider, index, config) {
}
GpuIndexFlatL2::GpuIndexFlatL2(std::shared_ptr<GpuResources> resources,
faiss::IndexFlatL2* index,
GpuIndexFlatConfig config) :
GpuIndexFlat(resources, index, config) {
}
GpuIndexFlatL2::GpuIndexFlatL2(GpuResources* resources,
GpuIndexFlatL2::GpuIndexFlatL2(GpuResourcesProvider* provider,
int dims,
GpuIndexFlatConfig config) :
GpuIndexFlat(provider, dims, faiss::METRIC_L2, config) {
}
GpuIndexFlatL2::GpuIndexFlatL2(std::shared_ptr<GpuResources> resources,
int dims,
GpuIndexFlatConfig config) :
GpuIndexFlat(resources, dims, faiss::METRIC_L2, config) {
@@ -332,13 +383,25 @@ GpuIndexFlatL2::copyTo(faiss::IndexFlat* index) {
// GpuIndexFlatIP
//
GpuIndexFlatIP::GpuIndexFlatIP(GpuResources* resources,
GpuIndexFlatIP::GpuIndexFlatIP(GpuResourcesProvider* provider,
faiss::IndexFlatIP* index,
GpuIndexFlatConfig config) :
GpuIndexFlat(provider, index, config) {
}
GpuIndexFlatIP::GpuIndexFlatIP(std::shared_ptr<GpuResources> resources,
faiss::IndexFlatIP* index,
GpuIndexFlatConfig config) :
GpuIndexFlat(resources, index, config) {
}
GpuIndexFlatIP::GpuIndexFlatIP(GpuResources* resources,
GpuIndexFlatIP::GpuIndexFlatIP(GpuResourcesProvider* provider,
int dims,
GpuIndexFlatConfig config) :
GpuIndexFlat(provider, dims, faiss::METRIC_INNER_PRODUCT, config) {
}
GpuIndexFlatIP::GpuIndexFlatIP(std::shared_ptr<GpuResources> resources,
int dims,
GpuIndexFlatConfig config) :
GpuIndexFlat(resources, dims, faiss::METRIC_INNER_PRODUCT, config) {
+35 -10
View File
@@ -9,6 +9,7 @@
#pragma once
#include <faiss/gpu/GpuIndex.h>
#include <memory>
namespace faiss {
@@ -47,12 +48,21 @@ class GpuIndexFlat : public GpuIndex {
public:
/// Construct from a pre-existing faiss::IndexFlat instance, copying
/// data over to the given GPU
GpuIndexFlat(GpuResources* resources,
GpuIndexFlat(GpuResourcesProvider* provider,
const faiss::IndexFlat* index,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
GpuIndexFlat(std::shared_ptr<GpuResources> resources,
const faiss::IndexFlat* index,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
/// Construct an empty instance that can be added to
GpuIndexFlat(GpuResources* resources,
GpuIndexFlat(GpuResourcesProvider* provider,
int dims,
faiss::MetricType metric,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
GpuIndexFlat(std::shared_ptr<GpuResources> resources,
int dims,
faiss::MetricType metric,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
@@ -100,7 +110,7 @@ class GpuIndexFlat : public GpuIndex {
const faiss::Index::idx_t* keys) const override;
/// For internal access
inline FlatIndex* getGpuData() { return data_; }
inline FlatIndex* getGpuData() { return data_.get(); }
protected:
/// Flat index does not require IDs as there is no storage available for them
@@ -122,9 +132,8 @@ class GpuIndexFlat : public GpuIndex {
/// Our config object
const GpuIndexFlatConfig config_;
/// Holds our GPU data containing the list of vectors; is managed via raw
/// pointer so as to allow non-CUDA compilers to see this header
FlatIndex* data_;
/// Holds our GPU data containing the list of vectors
std::unique_ptr<FlatIndex> data_;
};
/// Wrapper around the GPU implementation that looks like
@@ -134,12 +143,20 @@ class GpuIndexFlatL2 : public GpuIndexFlat {
public:
/// Construct from a pre-existing faiss::IndexFlatL2 instance, copying
/// data over to the given GPU
GpuIndexFlatL2(GpuResources* resources,
GpuIndexFlatL2(GpuResourcesProvider* provider,
faiss::IndexFlatL2* index,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
GpuIndexFlatL2(std::shared_ptr<GpuResources> resources,
faiss::IndexFlatL2* index,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
/// Construct an empty instance that can be added to
GpuIndexFlatL2(GpuResources* resources,
GpuIndexFlatL2(GpuResourcesProvider* provider,
int dims,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
GpuIndexFlatL2(std::shared_ptr<GpuResources> resources,
int dims,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
@@ -159,12 +176,20 @@ class GpuIndexFlatIP : public GpuIndexFlat {
public:
/// Construct from a pre-existing faiss::IndexFlatIP instance, copying
/// data over to the given GPU
GpuIndexFlatIP(GpuResources* resources,
GpuIndexFlatIP(GpuResourcesProvider* provider,
faiss::IndexFlatIP* index,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
GpuIndexFlatIP(std::shared_ptr<GpuResources> resources,
faiss::IndexFlatIP* index,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
/// Construct an empty instance that can be added to
GpuIndexFlatIP(GpuResources* resources,
GpuIndexFlatIP(GpuResourcesProvider* provider,
int dims,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
GpuIndexFlatIP(std::shared_ptr<GpuResources> resources,
int dims,
GpuIndexFlatConfig config = GpuIndexFlatConfig());
+5 -4
View File
@@ -16,17 +16,18 @@
namespace faiss { namespace gpu {
GpuIndexIVF::GpuIndexIVF(GpuResources* resources,
GpuIndexIVF::GpuIndexIVF(GpuResourcesProvider* provider,
int dims,
faiss::MetricType metric,
float metricArg,
int nlistIn,
GpuIndexIVFConfig config) :
GpuIndex(resources, dims, metric, metricArg, config),
ivfConfig_(std::move(config)),
GpuIndex(provider->getResources(),
dims, metric, metricArg, config),
nlist(nlistIn),
nprobe(1),
quantizer(nullptr) {
quantizer(nullptr),
ivfConfig_(std::move(config)) {
init_();
// Only IP and L2 are supported for now
+1 -2
View File
@@ -18,7 +18,6 @@ namespace faiss { struct IndexIVF; }
namespace faiss { namespace gpu {
class GpuIndexFlat;
class GpuResources;
struct GpuIndexIVFConfig : public GpuIndexConfig {
inline GpuIndexIVFConfig()
@@ -34,7 +33,7 @@ struct GpuIndexIVFConfig : public GpuIndexConfig {
class GpuIndexIVF : public GpuIndex {
public:
GpuIndexIVF(GpuResources* resources,
GpuIndexIVF(GpuResourcesProvider* provider,
int dims,
faiss::MetricType metric,
float metricArg,
+23 -27
View File
@@ -20,30 +20,28 @@
namespace faiss { namespace gpu {
GpuIndexIVFFlat::GpuIndexIVFFlat(GpuResources* resources,
GpuIndexIVFFlat::GpuIndexIVFFlat(GpuResourcesProvider* provider,
const faiss::IndexIVFFlat* index,
GpuIndexIVFFlatConfig config) :
GpuIndexIVF(resources,
GpuIndexIVF(provider,
index->d,
index->metric_type,
index->metric_arg,
index->nlist,
config),
ivfFlatConfig_(config),
reserveMemoryVecs_(0),
index_(nullptr) {
reserveMemoryVecs_(0) {
copyFrom(index);
}
GpuIndexIVFFlat::GpuIndexIVFFlat(GpuResources* resources,
GpuIndexIVFFlat::GpuIndexIVFFlat(GpuResourcesProvider* provider,
int dims,
int nlist,
faiss::MetricType metric,
GpuIndexIVFFlatConfig config) :
GpuIndexIVF(resources, dims, metric, 0, nlist, config),
GpuIndexIVF(provider, dims, metric, 0, nlist, config),
ivfFlatConfig_(config),
reserveMemoryVecs_(0),
index_(nullptr) {
reserveMemoryVecs_(0) {
// faiss::Index params
this->is_trained = false;
@@ -53,7 +51,6 @@ GpuIndexIVFFlat::GpuIndexIVFFlat(GpuResources* resources,
}
GpuIndexIVFFlat::~GpuIndexIVFFlat() {
delete index_;
}
void
@@ -72,8 +69,7 @@ GpuIndexIVFFlat::copyFrom(const faiss::IndexIVFFlat* index) {
GpuIndexIVF::copyFrom(index);
// Clear out our old data
delete index_;
index_ = nullptr;
index_.reset();
// The other index might not be trained
if (!index->is_trained) {
@@ -85,14 +81,14 @@ GpuIndexIVFFlat::copyFrom(const faiss::IndexIVFFlat* index) {
FAISS_ASSERT(is_trained);
// Copy our lists as well
index_ = new IVFFlat(resources_,
quantizer->getGpuData(),
index->metric_type,
index->metric_arg,
false, // no residual
nullptr, // no scalar quantizer
ivfFlatConfig_.indicesOptions,
memorySpace_);
index_.reset(new IVFFlat(resources_.get(),
quantizer->getGpuData(),
index->metric_type,
index->metric_arg,
false, // no residual
nullptr, // no scalar quantizer
ivfFlatConfig_.indicesOptions,
memorySpace_));
InvertedLists *ivf = index->invlists;
for (size_t i = 0; i < ivf->nlist; ++i) {
@@ -181,14 +177,14 @@ GpuIndexIVFFlat::train(Index::idx_t n, const float* x) {
trainQuantizer_(n, x);
// The quantizer is now trained; construct the IVF index
index_ = new IVFFlat(resources_,
quantizer->getGpuData(),
this->metric_type,
this->metric_arg,
false, // no residual
nullptr, // no scalar quantizer
ivfFlatConfig_.indicesOptions,
memorySpace_);
index_.reset(new IVFFlat(resources_.get(),
quantizer->getGpuData(),
this->metric_type,
this->metric_arg,
false, // no residual
nullptr, // no scalar quantizer
ivfFlatConfig_.indicesOptions,
memorySpace_));
if (reserveMemoryVecs_) {
index_->reserveMemory(reserveMemoryVecs_);
+4 -3
View File
@@ -9,6 +9,7 @@
#pragma once
#include <faiss/gpu/GpuIndexIVF.h>
#include <memory>
namespace faiss { struct IndexIVFFlat; }
@@ -26,13 +27,13 @@ class GpuIndexIVFFlat : public GpuIndexIVF {
public:
/// Construct from a pre-existing faiss::IndexIVFFlat instance, copying
/// data over to the given GPU, if the input index is trained.
GpuIndexIVFFlat(GpuResources* resources,
GpuIndexIVFFlat(GpuResourcesProvider* provider,
const faiss::IndexIVFFlat* index,
GpuIndexIVFFlatConfig config = GpuIndexIVFFlatConfig());
/// Constructs a new instance with an empty flat quantizer; the user
/// provides the number of lists desired.
GpuIndexIVFFlat(GpuResources* resources,
GpuIndexIVFFlat(GpuResourcesProvider* provider,
int dims,
int nlist,
faiss::MetricType metric,
@@ -79,7 +80,7 @@ class GpuIndexIVFFlat : public GpuIndexIVF {
size_t reserveMemoryVecs_;
/// Instance that we own; contains the inverted list
IVFFlat* index_;
std::unique_ptr<IVFFlat> index_;
};
} } // namespace
+29 -35
View File
@@ -20,10 +20,10 @@
namespace faiss { namespace gpu {
GpuIndexIVFPQ::GpuIndexIVFPQ(GpuResources* resources,
GpuIndexIVFPQ::GpuIndexIVFPQ(GpuResourcesProvider* provider,
const faiss::IndexIVFPQ* index,
GpuIndexIVFPQConfig config) :
GpuIndexIVF(resources,
GpuIndexIVF(provider,
index->d,
index->metric_type,
index->metric_arg,
@@ -32,19 +32,18 @@ GpuIndexIVFPQ::GpuIndexIVFPQ(GpuResources* resources,
ivfpqConfig_(config),
subQuantizers_(0),
bitsPerCode_(0),
reserveMemoryVecs_(0),
index_(nullptr) {
reserveMemoryVecs_(0) {
copyFrom(index);
}
GpuIndexIVFPQ::GpuIndexIVFPQ(GpuResources* resources,
GpuIndexIVFPQ::GpuIndexIVFPQ(GpuResourcesProvider* provider,
int dims,
int nlist,
int subQuantizers,
int bitsPerCode,
faiss::MetricType metric,
GpuIndexIVFPQConfig config) :
GpuIndexIVF(resources,
GpuIndexIVF(provider,
dims,
metric,
0,
@@ -53,8 +52,7 @@ GpuIndexIVFPQ::GpuIndexIVFPQ(GpuResources* resources,
ivfpqConfig_(config),
subQuantizers_(subQuantizers),
bitsPerCode_(bitsPerCode),
reserveMemoryVecs_(0),
index_(nullptr) {
reserveMemoryVecs_(0) {
verifySettings_();
// We haven't trained ourselves, so don't construct the PQ index yet
@@ -62,7 +60,6 @@ GpuIndexIVFPQ::GpuIndexIVFPQ(GpuResources* resources,
}
GpuIndexIVFPQ::~GpuIndexIVFPQ() {
delete index_;
}
void
@@ -72,8 +69,7 @@ GpuIndexIVFPQ::copyFrom(const faiss::IndexIVFPQ* index) {
GpuIndexIVF::copyFrom(index);
// Clear out our old data
delete index_;
index_ = nullptr;
index_.reset();
subQuantizers_ = index->pq.M;
bitsPerCode_ = index->pq.nbits;
@@ -98,16 +94,16 @@ GpuIndexIVFPQ::copyFrom(const faiss::IndexIVFPQ* index) {
// Copy our lists as well
// The product quantizer must have data in it
FAISS_ASSERT(index->pq.centroids.size() > 0);
index_ = new IVFPQ(resources_,
index->metric_type,
index->metric_arg,
quantizer->getGpuData(),
subQuantizers_,
bitsPerCode_,
(float*) index->pq.centroids.data(),
ivfpqConfig_.indicesOptions,
ivfpqConfig_.useFloat16LookupTables,
memorySpace_);
index_.reset(new IVFPQ(resources_.get(),
index->metric_type,
index->metric_arg,
quantizer->getGpuData(),
subQuantizers_,
bitsPerCode_,
(float*) index->pq.centroids.data(),
ivfpqConfig_.indicesOptions,
ivfpqConfig_.useFloat16LookupTables,
memorySpace_));
// Doesn't make sense to reserve memory here
index_->setPrecomputedCodes(ivfpqConfig_.usePrecomputedTables);
@@ -126,7 +122,7 @@ GpuIndexIVFPQ::copyFrom(const faiss::IndexIVFPQ* index) {
list_size);
index_->addCodeVectorsFromCpu(
i, ivf->get_codes(i), ivf->get_ids(i), list_size);
i, ivf->get_codes(i), ivf->get_ids(i), list_size);
}
}
@@ -157,9 +153,7 @@ GpuIndexIVFPQ::copyTo(faiss::IndexIVFPQ* index) const {
index->polysemous_ht = 0;
index->precomputed_table.clear();
InvertedLists *ivf = new ArrayInvertedLists(
nlist, index->code_size);
InvertedLists *ivf = new ArrayInvertedLists(nlist, index->code_size);
index->replace_invlists(ivf, true);
if (index_) {
@@ -276,16 +270,16 @@ GpuIndexIVFPQ::trainResidualQuantizer_(Index::idx_t n, const float* x) {
pq.verbose = this->verbose;
pq.train(n, residuals.data());
index_ = new IVFPQ(resources_,
metric_type,
metric_arg,
quantizer->getGpuData(),
subQuantizers_,
bitsPerCode_,
pq.centroids.data(),
ivfpqConfig_.indicesOptions,
ivfpqConfig_.useFloat16LookupTables,
memorySpace_);
index_.reset(new IVFPQ(resources_.get(),
metric_type,
metric_arg,
quantizer->getGpuData(),
subQuantizers_,
bitsPerCode_,
pq.centroids.data(),
ivfpqConfig_.indicesOptions,
ivfpqConfig_.useFloat16LookupTables,
memorySpace_));
if (reserveMemoryVecs_) {
index_->reserveMemory(reserveMemoryVecs_);
}
+4 -3
View File
@@ -9,6 +9,7 @@
#pragma once
#include <faiss/gpu/GpuIndexIVF.h>
#include <memory>
#include <vector>
namespace faiss { struct IndexIVFPQ; }
@@ -39,12 +40,12 @@ class GpuIndexIVFPQ : public GpuIndexIVF {
public:
/// Construct from a pre-existing faiss::IndexIVFPQ instance, copying
/// data over to the given GPU, if the input index is trained.
GpuIndexIVFPQ(GpuResources* resources,
GpuIndexIVFPQ(GpuResourcesProvider* provider,
const faiss::IndexIVFPQ* index,
GpuIndexIVFPQConfig config = GpuIndexIVFPQConfig());
/// Construct an empty index
GpuIndexIVFPQ(GpuResources* resources,
GpuIndexIVFPQ(GpuResourcesProvider* provider,
int dims,
int nlist,
int subQuantizers,
@@ -137,7 +138,7 @@ class GpuIndexIVFPQ : public GpuIndexIVF {
/// The product quantizer instance that we own; contains the
/// inverted lists
IVFPQ* index_;
std::unique_ptr<IVFPQ> index_;
};
} } // namespace
+25 -30
View File
@@ -18,20 +18,19 @@
namespace faiss { namespace gpu {
GpuIndexIVFScalarQuantizer::GpuIndexIVFScalarQuantizer(
GpuResources* resources,
GpuResourcesProvider* provider,
const faiss::IndexIVFScalarQuantizer* index,
GpuIndexIVFScalarQuantizerConfig config) :
GpuIndexIVF(resources,
GpuIndexIVF(provider,
index->d,
index->metric_type,
index->metric_arg,
index->nlist,
config),
ivfSQConfig_(config),
sq(index->sq),
by_residual(index->by_residual),
reserveMemoryVecs_(0),
index_(nullptr) {
ivfSQConfig_(config),
reserveMemoryVecs_(0) {
copyFrom(index);
FAISS_THROW_IF_NOT_MSG(isSQSupported(sq.qtype),
@@ -39,20 +38,18 @@ GpuIndexIVFScalarQuantizer::GpuIndexIVFScalarQuantizer(
}
GpuIndexIVFScalarQuantizer::GpuIndexIVFScalarQuantizer(
GpuResources* resources,
GpuResourcesProvider* provider,
int dims,
int nlist,
faiss::ScalarQuantizer::QuantizerType qtype,
faiss::MetricType metric,
bool encodeResidual,
GpuIndexIVFScalarQuantizerConfig config) :
GpuIndexIVF(resources, dims, metric, 0, nlist, config),
ivfSQConfig_(config),
GpuIndexIVF(provider, dims, metric, 0, nlist, config),
sq(dims, qtype),
by_residual(encodeResidual),
reserveMemoryVecs_(0),
index_(nullptr) {
ivfSQConfig_(config),
reserveMemoryVecs_(0) {
// faiss::Index params
this->is_trained = false;
@@ -63,7 +60,6 @@ GpuIndexIVFScalarQuantizer::GpuIndexIVFScalarQuantizer(
}
GpuIndexIVFScalarQuantizer::~GpuIndexIVFScalarQuantizer() {
delete index_;
}
void
@@ -81,8 +77,7 @@ GpuIndexIVFScalarQuantizer::copyFrom(
DeviceScope scope(device_);
// Clear out our old data
delete index_;
index_ = nullptr;
index_.reset();
// Copy what we need from the CPU index
GpuIndexIVF::copyFrom(index);
@@ -99,14 +94,14 @@ GpuIndexIVFScalarQuantizer::copyFrom(
this->is_trained = true;
// Copy our lists as well
index_ = new IVFFlat(resources_,
quantizer->getGpuData(),
index->metric_type,
index->metric_arg,
by_residual,
&sq,
ivfSQConfig_.indicesOptions,
memorySpace_);
index_.reset(new IVFFlat(resources_.get(),
quantizer->getGpuData(),
index->metric_type,
index->metric_arg,
by_residual,
&sq,
ivfSQConfig_.indicesOptions,
memorySpace_));
InvertedLists* ivf = index->invlists;
@@ -215,14 +210,14 @@ GpuIndexIVFScalarQuantizer::train(Index::idx_t n, const float* x) {
trainResiduals_(n, hostData.data());
// The quantizer is now trained; construct the IVF index
index_ = new IVFFlat(resources_,
quantizer->getGpuData(),
this->metric_type,
this->metric_arg,
by_residual,
&sq,
ivfSQConfig_.indicesOptions,
memorySpace_);
index_.reset(new IVFFlat(resources_.get(),
quantizer->getGpuData(),
this->metric_type,
this->metric_arg,
by_residual,
&sq,
ivfSQConfig_.indicesOptions,
memorySpace_));
if (reserveMemoryVecs_) {
index_->reserveMemory(reserveMemoryVecs_);
+4 -3
View File
@@ -10,6 +10,7 @@
#include <faiss/gpu/GpuIndexIVF.h>
#include <faiss/IndexScalarQuantizer.h>
#include <memory>
namespace faiss { namespace gpu {
@@ -26,7 +27,7 @@ class GpuIndexIVFScalarQuantizer : public GpuIndexIVF {
/// Construct from a pre-existing faiss::IndexIVFScalarQuantizer instance,
/// copying data over to the given GPU, if the input index is trained.
GpuIndexIVFScalarQuantizer(
GpuResources* resources,
GpuResourcesProvider* provider,
const faiss::IndexIVFScalarQuantizer* index,
GpuIndexIVFScalarQuantizerConfig config =
GpuIndexIVFScalarQuantizerConfig());
@@ -34,7 +35,7 @@ class GpuIndexIVFScalarQuantizer : public GpuIndexIVF {
/// Constructs a new instance with an empty flat quantizer; the user
/// provides the number of lists desired.
GpuIndexIVFScalarQuantizer(
GpuResources* resources,
GpuResourcesProvider* provider,
int dims,
int nlist,
faiss::ScalarQuantizer::QuantizerType qtype,
@@ -94,7 +95,7 @@ class GpuIndexIVFScalarQuantizer : public GpuIndexIVF {
size_t reserveMemoryVecs_;
/// Instance that we own; contains the inverted list
IVFFlat* index_;
std::unique_ptr<IVFFlat> index_;
};
} } // namespace
+99 -5
View File
@@ -11,6 +11,87 @@
namespace faiss { namespace gpu {
AllocInfo makeDevAlloc(AllocType at, cudaStream_t st) {
return AllocInfo(at, getCurrentDevice(), MemorySpace::Device, st);
}
AllocInfo makeTempAlloc(AllocType at, cudaStream_t st) {
return AllocInfo(at, getCurrentDevice(), MemorySpace::Temporary, st);
}
AllocInfo makeSpaceAlloc(AllocType at, MemorySpace sp, cudaStream_t st) {
return AllocInfo(at, getCurrentDevice(), sp, st);
}
//
// GpuMemoryReservation
//
GpuMemoryReservation::GpuMemoryReservation()
: res(nullptr),
device(0),
stream(nullptr),
data(nullptr),
size(0) {
}
GpuMemoryReservation::GpuMemoryReservation(GpuResources* r,
int dev,
cudaStream_t str,
void* p,
size_t sz)
: res(r),
device(dev),
stream(str),
data(p),
size(sz) {
}
GpuMemoryReservation::GpuMemoryReservation(GpuMemoryReservation&& m) noexcept {
res = m.res; m.res = nullptr;
device = m.device; m.device = 0;
stream = m.stream; m.stream = nullptr;
data = m.data; m.data = nullptr;
size = m.size; m.size = 0;
}
GpuMemoryReservation&
GpuMemoryReservation::operator=(GpuMemoryReservation&& m) {
// Can't be both a valid allocation and the same allocation
FAISS_ASSERT(!(res && res == m.res && device == m.device && data == m.data));
release();
res = m.res; m.res = nullptr;
device = m.device; m.device = 0;
stream = m.stream; m.stream = nullptr;
data = m.data; m.data = nullptr;
size = m.size; m.size = 0;
return *this;
}
void
GpuMemoryReservation::release() {
if (res) {
res->deallocMemory(device, data);
res = nullptr;
device = 0;
stream = nullptr;
data = nullptr;
size = 0;
}
}
GpuMemoryReservation::~GpuMemoryReservation() {
if (res) {
res->deallocMemory(device, data);
}
}
//
// GpuResources
//
GpuResources::~GpuResources() {
}
@@ -29,11 +110,6 @@ GpuResources::getAlternateStreamsCurrentDevice() {
return getAlternateStreams(getCurrentDevice());
}
DeviceMemory&
GpuResources::getMemoryManagerCurrentDevice() {
return getMemoryManager(getCurrentDevice());
}
cudaStream_t
GpuResources::getAsyncCopyStreamCurrentDevice() {
return getAsyncCopyStream(getCurrentDevice());
@@ -49,4 +125,22 @@ GpuResources::syncDefaultStreamCurrentDevice() {
syncDefaultStream(getCurrentDevice());
}
GpuMemoryReservation
GpuResources::allocMemoryHandle(const AllocRequest& req) {
return GpuMemoryReservation(
this, req.device, req.stream, allocMemory(req), req.size);
}
size_t
GpuResources::getTempMemoryAvailableCurrentDevice() const {
return getTempMemoryAvailable(getCurrentDevice());
}
//
// GpuResourcesProvider
//
GpuResourcesProvider::~GpuResourcesProvider() {
}
} } // namespace
+189 -7
View File
@@ -8,16 +8,171 @@
#pragma once
#include <faiss/gpu/utils/DeviceMemory.h>
#include <faiss/impl/FaissAssert.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <memory>
#include <utility>
#include <vector>
namespace faiss { namespace gpu {
class GpuResources;
enum AllocType {
/// Unknown allocation type or miscellaneous (not currently categorized)
Other = 0,
/// Primary data storage for GpuIndexFlat (the raw matrix of vectors and
/// vector norms if needed)
FlatData = 1,
/// Primary data storage for GpuIndexIVF* (the storage for each individual IVF
/// list)
IVFLists = 2,
/// Quantizer (PQ, SQ) dictionary information
Quantizer = 3,
/// For GpuIndexIVFPQ, "precomputed codes" for more efficient PQ lookup
/// require the use of possibly large tables. These are marked separately from
/// Quantizer as these can frequently be 100s - 1000s of MiB in size
QuantizerPrecomputedCodes = 4,
///
/// StandardGpuResources implementation specific types
///
/// When using StandardGpuResources, temporary memory allocations
/// (MemorySpace::Temporary) come out of a stack region of memory that is
/// allocated up front for each gpu (e.g., 1.5 GiB upon initialization). This
/// allocation by StandardGpuResources is marked with this AllocType.
TemporaryMemoryBuffer = 10,
/// When using StandardGpuResources, any MemorySpace::Temporary allocations
/// that cannot be satisfied within the TemporaryMemoryBuffer region fall back
/// to calling cudaMalloc which are sized to just the request at hand. These
/// "overflow" temporary allocations are marked with this AllocType.
TemporaryMemoryOverflow = 11,
};
/// Convert an AllocType to string
std::string allocTypeToString(AllocType t);
/// Memory regions accessible to the GPU
enum MemorySpace {
/// Temporary device memory (guaranteed to no longer be used upon exit of a
/// top-level index call, and where the streams using it have completed GPU
/// work). Typically backed by Device memory (cudaMalloc/cudaFree).
Temporary = 0,
/// Managed using cudaMalloc/cudaFree (typical GPU device memory)
Device = 1,
/// Managed using cudaMallocManaged/cudaFree (typical Unified CPU/GPU memory)
Unified = 2,
};
/// Information on what/where an allocation is
struct AllocInfo {
inline AllocInfo()
: type(AllocType::Other),
device(0),
space(MemorySpace::Device),
stream(nullptr) {
}
inline AllocInfo(AllocType at,
int dev,
MemorySpace sp,
cudaStream_t st)
: type(at),
device(dev),
space(sp),
stream(st) {
}
/// The internal category of the allocation
AllocType type;
/// The device on which the allocation is happening
int device;
/// The memory space of the allocation
MemorySpace space;
/// The stream on which new work on the memory will be ordered (e.g., if a
/// piece of memory cached and to be returned for this call was last used on
/// stream 3 and a new memory request is for stream 4, the memory manager will
/// synchronize stream 4 to wait for the completion of stream 3 via events or
/// other stream synchronization.
///
/// The memory manager guarantees that the returned memory is free to use
/// without data races on this stream specified.
cudaStream_t stream;
};
/// Create an AllocInfo for the current device with MemorySpace::Device
AllocInfo makeDevAlloc(AllocType at, cudaStream_t st);
/// Create an AllocInfo for the current device with MemorySpace::Temporary
AllocInfo makeTempAlloc(AllocType at, cudaStream_t st);
/// Create an AllocInfo for the current device
AllocInfo makeSpaceAlloc(AllocType at, MemorySpace sp, cudaStream_t st);
/// Information on what/where an allocation is, along with how big it should be
struct AllocRequest : public AllocInfo {
inline AllocRequest()
: AllocInfo(),
size(0) {
}
inline AllocRequest(const AllocInfo& info,
size_t sz)
: AllocInfo(info),
size(sz) {
}
inline AllocRequest(AllocType at,
int dev,
MemorySpace sp,
cudaStream_t st,
size_t sz)
: AllocInfo(at, dev, sp, st),
size(sz) {
}
/// The size in bytes of the allocation
size_t size;
};
/// A RAII object that manages a temporary memory request
struct GpuMemoryReservation {
GpuMemoryReservation();
GpuMemoryReservation(GpuResources* r,
int dev,
cudaStream_t str,
void* p,
size_t sz);
GpuMemoryReservation(GpuMemoryReservation&& m) noexcept;
~GpuMemoryReservation();
GpuMemoryReservation& operator=(GpuMemoryReservation&& m);
inline void* get() { return data; }
void release();
GpuResources* res;
int device;
cudaStream_t stream;
void* data;
size_t size;
};
/// Base class of GPU-side resource provider; hides provision of
/// cuBLAS handles, CUDA streams and a temporary memory manager
/// cuBLAS handles, CUDA streams and all device memory allocation performed
class GpuResources {
public:
virtual ~GpuResources();
@@ -37,8 +192,19 @@ class GpuResources {
/// Returns the set of alternative streams that we use for the given device
virtual std::vector<cudaStream_t> getAlternateStreams(int device) = 0;
/// Returns the temporary memory manager for the given device
virtual DeviceMemory& getMemoryManager(int device) = 0;
/// Memory management
/// Returns an allocation from the given memory space, ordered with respect to
/// the given stream (i.e., the first user will be a kernel in this stream).
/// All allocations are sized internally to be the next highest multiple of 16
/// bytes, and all allocations returned are guaranteed to be 16 byte aligned.
virtual void* allocMemory(const AllocRequest& req) = 0;
/// Returns a previous allocation
virtual void deallocMemory(int device, void* in) = 0;
/// For MemorySpace::Temporary, how much space is immediately available
/// without cudaMalloc allocation?
virtual size_t getTempMemoryAvailable(int device) const = 0;
/// Returns the available CPU pinned memory buffer
virtual std::pair<void*, size_t> getPinnedMemory() = 0;
@@ -46,12 +212,22 @@ class GpuResources {
/// Returns the stream on which we perform async CPU <-> GPU copies
virtual cudaStream_t getAsyncCopyStream(int device) = 0;
///
/// Functions provided by default
///
/// Calls getBlasHandle with the current device
cublasHandle_t getBlasHandleCurrentDevice();
/// Calls getDefaultStream with the current device
cudaStream_t getDefaultStreamCurrentDevice();
/// Calls getTempMemoryAvailable with the current device
size_t getTempMemoryAvailableCurrentDevice() const;
/// Returns a temporary memory allocation via a RAII object
GpuMemoryReservation allocMemoryHandle(const AllocRequest& req);
/// Synchronizes the CPU with respect to the default stream for the
/// given device
// equivalent to cudaDeviceSynchronize(getDefaultStream(device))
@@ -63,11 +239,17 @@ class GpuResources {
/// Calls getAlternateStreams for the current device
std::vector<cudaStream_t> getAlternateStreamsCurrentDevice();
/// Calls getMemoryManager for the current device
DeviceMemory& getMemoryManagerCurrentDevice();
/// Calls getAsyncCopyStream for the current device
cudaStream_t getAsyncCopyStreamCurrentDevice();
};
/// Interface for a provider of a shared resources object
class GpuResourcesProvider {
public:
virtual ~GpuResourcesProvider();
/// Returns the shared resources object
virtual std::shared_ptr<GpuResources> getResources() = 0;
};
} } // namespace
+311 -40
View File
@@ -8,9 +8,11 @@
#include <faiss/gpu/StandardGpuResources.h>
#include <faiss/gpu/utils/DeviceUtils.h>
#include <faiss/gpu/utils/MemorySpace.h>
#include <faiss/gpu/utils/StaticUtils.h>
#include <faiss/impl/FaissAssert.h>
#include <limits>
#include <iostream>
#include <sstream>
namespace faiss { namespace gpu {
@@ -31,9 +33,60 @@ constexpr size_t k8GiBTempMem = (size_t) 1024 * 1024 * 1024;
// Maximum temporary memory allocation for all GPUs
constexpr size_t kMaxTempMem = (size_t) 1536 * 1024 * 1024;
std::string allocsToString(const std::unordered_map<void*, AllocRequest>& map) {
// Produce a sorted list of all outstanding allocations by type
std::unordered_map<AllocType, std::pair<int, size_t>> stats;
for (auto& entry : map) {
auto& a = entry.second;
auto it = stats.find(a.type);
if (it != stats.end()) {
stats[a.type].first++;
stats[a.type].second += a.size;
} else {
stats[a.type] = std::make_pair(1, a.size);
}
}
std::stringstream ss;
for (auto& entry : stats) {
ss << "Alloc type " << allocTypeToString(entry.first) << ": "
<< entry.second.first << " allocations, "
<< entry.second.second << " bytes\n";
}
return ss.str();
}
StandardGpuResources::StandardGpuResources() :
}
std::string allocTypeToString(AllocType t) {
switch (t) {
case AllocType::Other:
return "Other";
case AllocType::FlatData:
return "FlatData";
case AllocType::IVFLists:
return "IVFLists";
case AllocType::Quantizer:
return "Quantizer";
case AllocType::QuantizerPrecomputedCodes:
return "QuantizerPrecomputedCodes";
case AllocType::TemporaryMemoryBuffer:
return "TemporaryMemoryBuffer";
case AllocType::TemporaryMemoryOverflow:
return "TemporaryMemoryOverflow";
default:
return "Unknown";
}
}
//
// StandardGpuResourcesImpl
//
StandardGpuResourcesImpl::StandardGpuResourcesImpl() :
pinnedMemAlloc_(nullptr),
pinnedMemAllocSize_(0),
// let the adjustment function determine the memory size for us by passing
@@ -44,7 +97,28 @@ StandardGpuResources::StandardGpuResources() :
cudaMallocWarning_(true) {
}
StandardGpuResources::~StandardGpuResources() {
StandardGpuResourcesImpl::~StandardGpuResourcesImpl() {
// The temporary memory allocator has allocated memory through us, so clean
// that up before we finish fully de-initializing ourselves
tempMemory_.clear();
// Make sure all allocations have been freed
bool allocError = false;
for (auto& entry : allocs_) {
auto& map = entry.second;
if (!map.empty()) {
std::cerr
<< "StandardGpuResources destroyed with allocations outstanding:\n"
<< "Device " << entry.first << " outstanding allocations:\n";
std::cerr << allocsToString(map);
allocError = true;
}
}
FAISS_ASSERT_MSG(!allocError, "GPU memory allocations not properly cleaned up");
for (auto& entry : defaultStreams_) {
DeviceScope scope(entry.first);
@@ -78,13 +152,16 @@ StandardGpuResources::~StandardGpuResources() {
}
if (pinnedMemAlloc_) {
freeMemorySpace(MemorySpace::HostPinned, pinnedMemAlloc_);
auto err = cudaFreeHost(pinnedMemAlloc_);
FAISS_ASSERT_FMT(err == cudaSuccess,
"Failed to cudaFreeHost pointer %p (error %d %s)",
pinnedMemAlloc_, (int) err, cudaGetErrorString(err));
}
}
size_t
StandardGpuResources::getDefaultTempMemForGPU(int device,
size_t requested) {
StandardGpuResourcesImpl::getDefaultTempMemForGPU(int device,
size_t requested) {
auto totalMem = device != -1 ?
getDeviceProperties(device).totalGlobalMem :
std::numeric_limits<size_t>::max();
@@ -113,13 +190,12 @@ StandardGpuResources::getDefaultTempMemForGPU(int device,
}
void
StandardGpuResources::noTempMemory() {
StandardGpuResourcesImpl::noTempMemory() {
setTempMemory(0);
setCudaMallocWarning(false);
}
void
StandardGpuResources::setTempMemory(size_t size) {
StandardGpuResourcesImpl::setTempMemory(size_t size) {
if (tempMemSize_ != size) {
// adjust based on general limits
tempMemSize_ = getDefaultTempMemForGPU(-1, size);
@@ -129,14 +205,15 @@ StandardGpuResources::setTempMemory(size_t size) {
// This should be safe to do, even if we are currently running work, because
// the cudaFree call that this implies will force-synchronize all GPUs with
// the CPU
for (auto& p : memory_) {
for (auto& p : tempMemory_) {
int device = p.first;
// Free the existing memory first
p.second.reset();
// Allocate new
p.second = std::unique_ptr<StackDeviceMemory>(
new StackDeviceMemory(p.first,
new StackDeviceMemory(this,
p.first,
// adjust for this specific device
getDefaultTempMemForGPU(device, tempMemSize_)));
}
@@ -144,7 +221,7 @@ StandardGpuResources::setTempMemory(size_t size) {
}
void
StandardGpuResources::setPinnedMemory(size_t size) {
StandardGpuResourcesImpl::setPinnedMemory(size_t size) {
// Should not call this after devices have been initialized
FAISS_ASSERT(defaultStreams_.size() == 0);
FAISS_ASSERT(!pinnedMemAlloc_);
@@ -153,7 +230,7 @@ StandardGpuResources::setPinnedMemory(size_t size) {
}
void
StandardGpuResources::setDefaultStream(int device, cudaStream_t stream) {
StandardGpuResourcesImpl::setDefaultStream(int device, cudaStream_t stream) {
auto it = defaultStreams_.find(device);
if (it != defaultStreams_.end()) {
// Replace this stream with the user stream
@@ -165,30 +242,21 @@ StandardGpuResources::setDefaultStream(int device, cudaStream_t stream) {
}
void
StandardGpuResources::setDefaultNullStreamAllDevices() {
StandardGpuResourcesImpl::setDefaultNullStreamAllDevices() {
for (int dev = 0; dev < getNumDevices(); ++dev) {
setDefaultStream(dev, nullptr);
}
}
void
StandardGpuResources::setCudaMallocWarning(bool b) {
cudaMallocWarning_ = b;
for (auto& v : memory_) {
v.second->setCudaMallocWarning(b);
}
}
bool
StandardGpuResources::isInitialized(int device) const {
StandardGpuResourcesImpl::isInitialized(int device) const {
// Use default streams as a marker for whether or not a certain
// device has been initialized
return defaultStreams_.count(device) != 0;
}
void
StandardGpuResources::initializeForDevice(int device) {
StandardGpuResourcesImpl::initializeForDevice(int device) {
if (isInitialized(device)) {
return;
}
@@ -196,7 +264,15 @@ StandardGpuResources::initializeForDevice(int device) {
// If this is the first device that we're initializing, create our
// pinned memory allocation
if (defaultStreams_.empty() && pinnedMemSize_ > 0) {
allocMemorySpace(MemorySpace::HostPinned, &pinnedMemAlloc_, pinnedMemSize_);
auto err =
cudaHostAlloc(&pinnedMemAlloc_, pinnedMemSize_, cudaHostAllocDefault);
FAISS_THROW_IF_NOT_FMT(
err == cudaSuccess,
"failed to cudaHostAlloc %zu bytes for CPU <-> GPU "
"async copy buffer (error %d %s)",
pinnedMemSize_, (int) err, cudaGetErrorString(err));
pinnedMemAllocSize_ = pinnedMemSize_;
}
@@ -255,49 +331,244 @@ StandardGpuResources::initializeForDevice(int device) {
}
#endif
FAISS_ASSERT(memory_.count(device) == 0);
FAISS_ASSERT(allocs_.count(device) == 0);
allocs_[device] = std::unordered_map<void*, AllocRequest>();
FAISS_ASSERT(tempMemory_.count(device) == 0);
auto mem = std::unique_ptr<StackDeviceMemory>(
new StackDeviceMemory(device,
new StackDeviceMemory(this,
device,
// adjust for this specific device
getDefaultTempMemForGPU(device, tempMemSize_)));
mem->setCudaMallocWarning(cudaMallocWarning_);
memory_.emplace(device, std::move(mem));
tempMemory_.emplace(device, std::move(mem));
}
cublasHandle_t
StandardGpuResources::getBlasHandle(int device) {
StandardGpuResourcesImpl::getBlasHandle(int device) {
initializeForDevice(device);
return blasHandles_[device];
}
cudaStream_t
StandardGpuResources::getDefaultStream(int device) {
StandardGpuResourcesImpl::getDefaultStream(int device) {
initializeForDevice(device);
return defaultStreams_[device];
}
std::vector<cudaStream_t>
StandardGpuResources::getAlternateStreams(int device) {
StandardGpuResourcesImpl::getAlternateStreams(int device) {
initializeForDevice(device);
return alternateStreams_[device];
}
DeviceMemory& StandardGpuResources::getMemoryManager(int device) {
initializeForDevice(device);
return *memory_[device];
}
std::pair<void*, size_t>
StandardGpuResources::getPinnedMemory() {
StandardGpuResourcesImpl::getPinnedMemory() {
return std::make_pair(pinnedMemAlloc_, pinnedMemAllocSize_);
}
cudaStream_t
StandardGpuResources::getAsyncCopyStream(int device) {
StandardGpuResourcesImpl::getAsyncCopyStream(int device) {
initializeForDevice(device);
return asyncCopyStreams_[device];
}
void*
StandardGpuResourcesImpl::allocMemory(const AllocRequest& req) {
initializeForDevice(req.device);
// We don't allocate a placeholder for zero-sized allocations
if (req.size == 0) {
return nullptr;
}
// Make sure that the allocation is a multiple of 16 bytes for alignment
// purposes
auto adjReq = req;
adjReq.size = utils::roundUp(adjReq.size, (size_t) 16);
void* p = nullptr;
if (adjReq.space == MemorySpace::Temporary) {
// If we don't have enough space in our temporary memory manager, we need
// to allocate this request separately
auto& tempMem = tempMemory_[adjReq.device];
if (adjReq.size > tempMem->getSizeAvailable()) {
// We need to allocate this ourselves
AllocRequest newReq = adjReq;
newReq.space = MemorySpace::Device;
newReq.type = AllocType::TemporaryMemoryOverflow;
return allocMemory(newReq);
}
// Otherwise, we can handle this locally
p = tempMemory_[adjReq.device]->allocMemory(adjReq.stream, adjReq.size);
} else if (adjReq.space == MemorySpace::Device) {
auto err = cudaMalloc(&p, adjReq.size);
// Throw if we fail to allocate
if (err != cudaSuccess) {
auto& map = allocs_[req.device];
std::stringstream ss;
ss << "Failed to cudaMalloc " << adjReq.size << " bytes "
<< "on device " << adjReq.device << " (error "
<< (int) err << " " << cudaGetErrorString(err)
<< "\nOutstanding allocations:\n" << allocsToString(map);
auto str = ss.str();
FAISS_THROW_IF_NOT_FMT(err == cudaSuccess, "%s", str.c_str());
}
} else if (adjReq.space == MemorySpace::Unified) {
auto err = cudaMallocManaged(&p, adjReq.size);
if (err != cudaSuccess) {
auto& map = allocs_[req.device];
std::stringstream ss;
ss << "Failed to cudaMallocManaged " << adjReq.size << " bytes "
<< "(error " << (int) err << " " << cudaGetErrorString(err)
<< "\nOutstanding allocations:\n" << allocsToString(map);
auto str = ss.str();
FAISS_THROW_IF_NOT_FMT(err == cudaSuccess, "%s", str.c_str());
}
} else {
FAISS_ASSERT_FMT(false, "unknown MemorySpace %d", (int) adjReq.space);
}
allocs_[adjReq.device][p] = adjReq;
return p;
}
void
StandardGpuResourcesImpl::deallocMemory(int device, void* p) {
FAISS_ASSERT(isInitialized(device));
if (!p) {
return;
}
auto& a = allocs_[device];
auto it = a.find(p);
if (it == a.end()) {
FAISS_ASSERT(it != a.end());
}
auto& req = it->second;
if (req.space == MemorySpace::Temporary) {
tempMemory_[device]->deallocMemory(device, req.stream, req.size, p);
} else if (req.space == MemorySpace::Device ||
req.space == MemorySpace::Unified) {
auto err = cudaFree(p);
FAISS_ASSERT_FMT(err == cudaSuccess,
"Failed to cudaFree pointer %p (error %d %s)",
p, (int) err, cudaGetErrorString(err));
} else {
FAISS_ASSERT_FMT(false, "unknown MemorySpace %d", (int) req.space);
}
a.erase(it);
}
size_t
StandardGpuResourcesImpl::getTempMemoryAvailable(int device) const {
FAISS_ASSERT(isInitialized(device));
auto it = tempMemory_.find(device);
FAISS_ASSERT(it != tempMemory_.end());
return it->second->getSizeAvailable();
}
std::map<int, std::map<std::string, std::pair<int, size_t>>>
StandardGpuResourcesImpl::getMemoryInfo() const {
using AT = std::map<std::string, std::pair<int, size_t>>;
std::map<int, AT> out;
for (auto& entry : allocs_) {
AT outDevice;
for (auto& a : entry.second) {
auto& v = outDevice[allocTypeToString(a.second.type)];
v.first++;
v.second += a.second.size;
}
out[entry.first] = std::move(outDevice);
}
return out;
}
//
// StandardGpuResources
//
StandardGpuResources::StandardGpuResources()
: res_(new StandardGpuResourcesImpl) {
}
StandardGpuResources::~StandardGpuResources() {
}
std::shared_ptr<GpuResources>
StandardGpuResources::getResources() {
return res_;
}
void
StandardGpuResources::noTempMemory() {
res_->noTempMemory();
}
void
StandardGpuResources::setTempMemory(size_t size) {
res_->setTempMemory(size);
}
void
StandardGpuResources::setPinnedMemory(size_t size) {
res_->setPinnedMemory(size);
}
void
StandardGpuResources::setDefaultStream(int device, cudaStream_t stream) {
res_->setDefaultStream(device, stream);
}
void
StandardGpuResources::setDefaultNullStreamAllDevices() {
res_->setDefaultNullStreamAllDevices();
}
std::map<int, std::map<std::string, std::pair<int, size_t>>>
StandardGpuResources::getMemoryInfo() const {
return res_->getMemoryInfo();
}
cudaStream_t
StandardGpuResources::getDefaultStream(int device) {
return res_->getDefaultStream(device);
}
size_t
StandardGpuResources::getTempMemoryAvailable(int device) const {
return res_->getTempMemoryAvailable(device);
}
void
StandardGpuResources::syncDefaultStreamCurrentDevice() {
res_->syncDefaultStreamCurrentDevice();
}
} } // namespace
+74 -14
View File
@@ -11,18 +11,20 @@
#include <faiss/gpu/GpuResources.h>
#include <faiss/gpu/utils/StackDeviceMemory.h>
#include <faiss/gpu/utils/DeviceUtils.h>
#include <functional>
#include <map>
#include <unordered_map>
#include <vector>
namespace faiss { namespace gpu {
/// Default implementation of GpuResources that allocates a cuBLAS
/// stream and 2 streams for use, as well as temporary memory
class StandardGpuResources : public GpuResources {
/// Standard implementation of the GpuResources object that provides for a
/// temporary memory manager
class StandardGpuResourcesImpl : public GpuResources {
public:
StandardGpuResources();
StandardGpuResourcesImpl();
~StandardGpuResources() override;
~StandardGpuResourcesImpl() override;
/// Disable allocation of temporary memory; all temporary memory
/// requests will call cudaMalloc / cudaFree at the point of use
@@ -46,10 +48,6 @@ class StandardGpuResources : public GpuResources {
/// for all devices
void setDefaultNullStreamAllDevices();
/// Enable or disable the warning about not having enough temporary memory
/// when cudaMalloc gets called
void setCudaMallocWarning(bool b);
public:
/// Internal system calls
@@ -62,7 +60,17 @@ class StandardGpuResources : public GpuResources {
std::vector<cudaStream_t> getAlternateStreams(int device) override;
DeviceMemory& getMemoryManager(int device) override;
/// Allocate non-temporary GPU memory
void* allocMemory(const AllocRequest& req) override;
/// Returns a previous allocation
void deallocMemory(int device, void* in) override;
size_t getTempMemoryAvailable(int device) const override;
/// Export a description of memory used for Python
std::map<int, std::map<std::string, std::pair<int, size_t>>>
getMemoryInfo() const;
std::pair<void*, size_t> getPinnedMemory() override;
@@ -77,6 +85,13 @@ class StandardGpuResources : public GpuResources {
static size_t getDefaultTempMemForGPU(int device, size_t requested);
private:
/// Set of currently outstanding memory allocations per device
/// device -> (alloc request, allocated ptr)
std::unordered_map<int, std::unordered_map<void*, AllocRequest>> allocs_;
/// Temporary memory provider, per each device
std::unordered_map<int, std::unique_ptr<StackDeviceMemory>> tempMemory_;
/// Our default stream that work is ordered on, one per each device
std::unordered_map<int, cudaStream_t> defaultStreams_;
@@ -85,7 +100,7 @@ class StandardGpuResources : public GpuResources {
std::unordered_map<int, cudaStream_t> userDefaultStreams_;
/// Other streams we can use, per each device
std::unordered_map<int, std::vector<cudaStream_t> > alternateStreams_;
std::unordered_map<int, std::vector<cudaStream_t>> alternateStreams_;
/// Async copy stream to use for GPU <-> CPU pinned memory copies
std::unordered_map<int, cudaStream_t> asyncCopyStreams_;
@@ -93,9 +108,6 @@ class StandardGpuResources : public GpuResources {
/// cuBLAS handle for each device
std::unordered_map<int, cublasHandle_t> blasHandles_;
/// Temporary memory provider, per each device
std::unordered_map<int, std::unique_ptr<StackDeviceMemory> > memory_;
/// Pinned memory allocation for use with this GPU
void* pinnedMemAlloc_;
size_t pinnedMemAllocSize_;
@@ -111,4 +123,52 @@ class StandardGpuResources : public GpuResources {
bool cudaMallocWarning_;
};
/// Default implementation of GpuResources that allocates a cuBLAS
/// stream and 2 streams for use, as well as temporary memory
class StandardGpuResources : public GpuResourcesProvider {
public:
StandardGpuResources();
~StandardGpuResources() override;
std::shared_ptr<GpuResources> getResources() override;
/// Disable allocation of temporary memory; all temporary memory
/// requests will call cudaMalloc / cudaFree at the point of use
void noTempMemory();
/// Specify that we wish to use a certain fixed size of memory on
/// all devices as temporary memory. This is the upper bound for the GPU
/// memory that we will reserve. We will never go above 1.5 GiB on any GPU;
/// smaller GPUs (with <= 4 GiB or <= 8 GiB) will use less memory than that.
/// To avoid any temporary memory allocation, pass 0.
void setTempMemory(size_t size);
/// Set amount of pinned memory to allocate, for async GPU <-> CPU
/// transfers
void setPinnedMemory(size_t size);
/// Called to change the stream for work ordering
void setDefaultStream(int device, cudaStream_t stream);
/// Called to change the work ordering streams to the null stream
/// for all devices
void setDefaultNullStreamAllDevices();
/// Export a description of memory used for Python
std::map<int, std::map<std::string, std::pair<int, size_t>>>
getMemoryInfo() const;
/// Returns the current default stream
cudaStream_t getDefaultStream(int device);
/// Returns the current amount of temp memory available
size_t getTempMemoryAvailable(int device) const;
/// Synchronize our default stream with the CPU
void syncDefaultStreamCurrentDevice();
private:
std::shared_ptr<StandardGpuResourcesImpl> res_;
};
} } // namespace
+7 -4
View File
@@ -18,9 +18,12 @@ BinaryFlatIndex::BinaryFlatIndex(GpuResources* res,
MemorySpace space) :
resources_(res),
dim_(dim),
space_(space),
num_(0),
rawData_(space) {
rawData_(res,
makeSpaceAlloc(
AllocType::FlatData,
space,
res->getDefaultStreamCurrentDevice())) {
FAISS_ASSERT(dim % 8 == 0);
}
@@ -74,14 +77,14 @@ BinaryFlatIndex::add(const unsigned char* data,
num_ += numVecs;
DeviceTensor<unsigned char, 2, true> vectors(
(unsigned char*) rawData_.data(), {(int) num_, (dim_ / 8)}, space_);
(unsigned char*) rawData_.data(), {(int) num_, (dim_ / 8)});
vectors_ = std::move(vectors);
}
void
BinaryFlatIndex::reset() {
rawData_.clear();
vectors_ = std::move(DeviceTensor<unsigned char, 2, true>());
vectors_ = DeviceTensor<unsigned char, 2, true>();
num_ = 0;
}
-4
View File
@@ -10,7 +10,6 @@
#include <faiss/gpu/utils/DeviceTensor.cuh>
#include <faiss/gpu/utils/DeviceVector.cuh>
#include <faiss/gpu/utils/MemorySpace.h>
namespace faiss { namespace gpu {
@@ -53,9 +52,6 @@ class BinaryFlatIndex {
/// Dimensionality of our vectors
const int dim_;
/// Memory space for our allocations
MemorySpace space_;
/// How many vectors we have
int num_;
+31 -29
View File
@@ -31,7 +31,7 @@ namespace faiss { namespace gpu {
template <typename T>
void runDistance(bool computeL2,
GpuResources* resources,
GpuResources* res,
Tensor<T, 2, true>& centroids,
bool centroidsRowMajor,
Tensor<float, 1, true>* centroidNorms,
@@ -57,8 +57,7 @@ void runDistance(bool computeL2,
FAISS_ASSERT(outDistances.getSize(1) == k);
FAISS_ASSERT(outIndices.getSize(1) == k);
auto& mem = resources->getMemoryManagerCurrentDevice();
auto defaultStream = resources->getDefaultStreamCurrentDevice();
auto defaultStream = res->getDefaultStreamCurrentDevice();
// If we're quering against a 0 sized set, just return empty results
if (centroids.numElements() == 0) {
@@ -76,9 +75,10 @@ void runDistance(bool computeL2,
// L2: If ||c||^2 is not pre-computed, calculate it
DeviceTensor<float, 1, true> cNorms;
if (computeL2 && !centroidNorms) {
cNorms =
std::move(DeviceTensor<float, 1, true>(
mem, {numCentroids}, defaultStream));
cNorms = DeviceTensor<float, 1, true>(
res,
makeTempAlloc(AllocType::Other, defaultStream),
{numCentroids});
runL2Norm(centroids, centroidsRowMajor, cNorms, true, defaultStream);
centroidNorms = &cNorms;
}
@@ -86,8 +86,10 @@ void runDistance(bool computeL2,
//
// Prepare norm vector ||q||^2; ||c||^2 is already pre-computed
//
int qNormSize[1] = {numQueries};
DeviceTensor<float, 1, true> queryNorms(mem, qNormSize, defaultStream);
DeviceTensor<float, 1, true> queryNorms(
res,
makeTempAlloc(AllocType::Other, defaultStream),
{(int) numQueries});
// ||q||^2
if (computeL2) {
@@ -102,7 +104,7 @@ void runDistance(bool computeL2,
numCentroids,
dim,
sizeof(T),
mem.getSizeAvailable(),
res->getTempMemoryAvailableCurrentDevice(),
tileRows,
tileCols);
@@ -114,27 +116,27 @@ void runDistance(bool computeL2,
// Temporary output memory space we'll use
DeviceTensor<float, 2, true> distanceBuf1(
mem, {tileRows, tileCols}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, tileCols});
DeviceTensor<float, 2, true> distanceBuf2(
mem, {tileRows, tileCols}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, tileCols});
DeviceTensor<float, 2, true>* distanceBufs[2] =
{&distanceBuf1, &distanceBuf2};
DeviceTensor<float, 2, true> outDistanceBuf1(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<float, 2, true> outDistanceBuf2(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<float, 2, true>* outDistanceBufs[2] =
{&outDistanceBuf1, &outDistanceBuf2};
DeviceTensor<int, 2, true> outIndexBuf1(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<int, 2, true> outIndexBuf2(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<int, 2, true>* outIndexBufs[2] =
{&outIndexBuf1, &outIndexBuf2};
auto streams = resources->getAlternateStreamsCurrentDevice();
auto streams = res->getAlternateStreamsCurrentDevice();
streamWait(streams, {defaultStream});
int curStream = 0;
@@ -196,7 +198,7 @@ void runDistance(bool computeL2,
centroidsRowMajor, // transposed MM if row major
computeL2 ? -2.0f : 1.0f,
0.0f,
resources->getBlasHandleCurrentDevice(),
res->getBlasHandleCurrentDevice(),
streams[curStream]);
if (computeL2) {
@@ -290,7 +292,7 @@ void runDistance(bool computeL2,
}
template <typename T>
void runL2Distance(GpuResources* resources,
void runL2Distance(GpuResources* res,
Tensor<T, 2, true>& centroids,
bool centroidsRowMajor,
Tensor<float, 1, true>* centroidNorms,
@@ -301,7 +303,7 @@ void runL2Distance(GpuResources* resources,
Tensor<int, 2, true>& outIndices,
bool ignoreOutDistances = false) {
runDistance<T>(true, // L2
resources,
res,
centroids,
centroidsRowMajor,
centroidNorms,
@@ -314,7 +316,7 @@ void runL2Distance(GpuResources* resources,
}
template <typename T>
void runIPDistance(GpuResources* resources,
void runIPDistance(GpuResources* res,
Tensor<T, 2, true>& centroids,
bool centroidsRowMajor,
Tensor<T, 2, true>& queries,
@@ -323,7 +325,7 @@ void runIPDistance(GpuResources* resources,
Tensor<float, 2, true>& outDistances,
Tensor<int, 2, true>& outIndices) {
runDistance<T>(false, // IP
resources,
res,
centroids,
centroidsRowMajor,
nullptr, // no centroid norms provided
@@ -340,7 +342,7 @@ void runIPDistance(GpuResources* resources,
//
void
runIPDistance(GpuResources* resources,
runIPDistance(GpuResources* res,
Tensor<float, 2, true>& vectors,
bool vectorsRowMajor,
Tensor<float, 2, true>& queries,
@@ -348,7 +350,7 @@ runIPDistance(GpuResources* resources,
int k,
Tensor<float, 2, true>& outDistances,
Tensor<int, 2, true>& outIndices) {
runIPDistance<float>(resources,
runIPDistance<float>(res,
vectors,
vectorsRowMajor,
queries,
@@ -359,7 +361,7 @@ runIPDistance(GpuResources* resources,
}
void
runIPDistance(GpuResources* resources,
runIPDistance(GpuResources* res,
Tensor<half, 2, true>& vectors,
bool vectorsRowMajor,
Tensor<half, 2, true>& queries,
@@ -367,7 +369,7 @@ runIPDistance(GpuResources* resources,
int k,
Tensor<float, 2, true>& outDistances,
Tensor<int, 2, true>& outIndices) {
runIPDistance<half>(resources,
runIPDistance<half>(res,
vectors,
vectorsRowMajor,
queries,
@@ -378,7 +380,7 @@ runIPDistance(GpuResources* resources,
}
void
runL2Distance(GpuResources* resources,
runL2Distance(GpuResources* res,
Tensor<float, 2, true>& vectors,
bool vectorsRowMajor,
Tensor<float, 1, true>* vectorNorms,
@@ -388,7 +390,7 @@ runL2Distance(GpuResources* resources,
Tensor<float, 2, true>& outDistances,
Tensor<int, 2, true>& outIndices,
bool ignoreOutDistances) {
runL2Distance<float>(resources,
runL2Distance<float>(res,
vectors,
vectorsRowMajor,
vectorNorms,
@@ -401,7 +403,7 @@ runL2Distance(GpuResources* resources,
}
void
runL2Distance(GpuResources* resources,
runL2Distance(GpuResources* res,
Tensor<half, 2, true>& vectors,
bool vectorsRowMajor,
Tensor<float, 1, true>* vectorNorms,
@@ -411,7 +413,7 @@ runL2Distance(GpuResources* resources,
Tensor<float, 2, true>& outDistances,
Tensor<int, 2, true>& outIndices,
bool ignoreOutDistances) {
runL2Distance<half>(resources,
runL2Distance<half>(res,
vectors,
vectorsRowMajor,
vectorNorms,
+41 -21
View File
@@ -28,7 +28,13 @@ FlatIndex::FlatIndex(GpuResources* res,
storeTransposed_(storeTransposed),
space_(space),
num_(0),
rawData_(space) {
rawData_(
res,
AllocInfo(
AllocType::FlatData,
getCurrentDevice(),
space,
res->getDefaultStreamCurrentDevice())) {
}
bool
@@ -101,7 +107,10 @@ FlatIndex::getVectorsFloat32Copy(cudaStream_t stream) {
DeviceTensor<float, 2, true>
FlatIndex::getVectorsFloat32Copy(int from, int num, cudaStream_t stream) {
DeviceTensor<float, 2, true> vecFloat32({num, dim_}, space_);
DeviceTensor<float, 2, true> vecFloat32(
resources_,
makeDevAlloc(AllocType::Other, stream),
{num, dim_});
if (useFloat16_) {
auto halfNarrow = vectorsHalf_.narrowOutermost(from, num);
@@ -122,12 +131,11 @@ FlatIndex::query(Tensor<float, 2, true>& input,
Tensor<int, 2, true>& outIndices,
bool exactDistance) {
auto stream = resources_->getDefaultStreamCurrentDevice();
auto& mem = resources_->getMemoryManagerCurrentDevice();
if (useFloat16_) {
// We need to convert the input to float16 for comparison to ourselves
auto inputHalf =
convertTensor<float, half, 2>(resources_, stream, input);
convertTensorTemporary<float, half, 2>(resources_, stream, input);
query(inputHalf, k, metric, metricArg,
outDistances, outIndices, exactDistance);
@@ -228,14 +236,14 @@ FlatIndex::add(const float* data, int numVecs, cudaStream_t stream) {
if (useFloat16_) {
// Make sure that `data` is on our device; we'll run the
// conversion on our device
auto devData = toDevice<float, 2>(resources_,
getCurrentDevice(),
(float*) data,
stream,
{numVecs, dim_});
auto devData = toDeviceTemporary<float, 2>(resources_,
getCurrentDevice(),
(float*) data,
stream,
{numVecs, dim_});
auto devDataHalf =
convertTensor<float, half, 2>(resources_, stream, devData);
convertTensorTemporary<float, half, 2>(resources_, stream, devData);
rawData_.append((char*) devDataHalf.data(),
devDataHalf.getSizeInBytes(),
@@ -252,33 +260,45 @@ FlatIndex::add(const float* data, int numVecs, cudaStream_t stream) {
if (useFloat16_) {
DeviceTensor<half, 2, true> vectorsHalf(
(half*) rawData_.data(), {(int) num_, dim_}, space_);
(half*) rawData_.data(), {(int) num_, dim_});
vectorsHalf_ = std::move(vectorsHalf);
} else {
DeviceTensor<float, 2, true> vectors(
(float*) rawData_.data(), {(int) num_, dim_}, space_);
(float*) rawData_.data(), {(int) num_, dim_});
vectors_ = std::move(vectors);
}
if (storeTransposed_) {
if (useFloat16_) {
vectorsHalfTransposed_ =
std::move(DeviceTensor<half, 2, true>({dim_, (int) num_}, space_));
DeviceTensor<half, 2, true>(
resources_,
makeSpaceAlloc(AllocType::FlatData, space_, stream),
{dim_, (int) num_});
runTransposeAny(vectorsHalf_, 0, 1, vectorsHalfTransposed_, stream);
} else {
vectorsTransposed_ =
std::move(DeviceTensor<float, 2, true>({dim_, (int) num_}, space_));
DeviceTensor<float, 2, true>(
resources_,
makeSpaceAlloc(AllocType::FlatData, space_, stream),
{dim_, (int)num_});
runTransposeAny(vectors_, 0, 1, vectorsTransposed_, stream);
}
}
// Precompute L2 norms of our database
if (useFloat16_) {
DeviceTensor<float, 1, true> norms({(int) num_}, space_);
DeviceTensor<float, 1, true> norms(
resources_,
makeSpaceAlloc(AllocType::FlatData, space_, stream),
{(int) num_});
runL2Norm(vectorsHalf_, true, norms, true, stream);
norms_ = std::move(norms);
} else {
DeviceTensor<float, 1, true> norms({(int) num_}, space_);
DeviceTensor<float, 1, true> norms(
resources_,
makeSpaceAlloc(AllocType::FlatData, space_, stream),
{(int) num_});
runL2Norm(vectors_, true, norms, true, stream);
norms_ = std::move(norms);
}
@@ -287,11 +307,11 @@ FlatIndex::add(const float* data, int numVecs, cudaStream_t stream) {
void
FlatIndex::reset() {
rawData_.clear();
vectors_ = std::move(DeviceTensor<float, 2, true>());
vectorsTransposed_ = std::move(DeviceTensor<float, 2, true>());
vectorsHalf_ = std::move(DeviceTensor<half, 2, true>());
vectorsHalfTransposed_ = std::move(DeviceTensor<half, 2, true>());
norms_ = std::move(DeviceTensor<float, 1, true>());
vectors_ = DeviceTensor<float, 2, true>();
vectorsTransposed_ = DeviceTensor<float, 2, true>();
vectorsHalf_ = DeviceTensor<half, 2, true>();
vectorsHalfTransposed_ = DeviceTensor<half, 2, true>();
norms_ = DeviceTensor<float, 1, true>();
num_ = 0;
}
+1 -1
View File
@@ -11,7 +11,7 @@
#include <faiss/MetricType.h>
#include <faiss/gpu/utils/DeviceTensor.cuh>
#include <faiss/gpu/utils/DeviceVector.cuh>
#include <faiss/gpu/utils/MemorySpace.h>
#include <faiss/gpu/GpuResources.h>
namespace faiss { namespace gpu {
+10 -11
View File
@@ -249,7 +249,7 @@ void runGeneralDistanceKernel(Tensor<T, 2, InnerContig>& vecs,
}
template <typename T, typename DistanceOp, bool InnerContig>
void runGeneralDistance(GpuResources* resources,
void runGeneralDistance(GpuResources* res,
Tensor<T, 2, InnerContig>& centroids,
Tensor<T, 2, InnerContig>& queries,
int k,
@@ -272,8 +272,7 @@ void runGeneralDistance(GpuResources* resources,
FAISS_ASSERT(outDistances.getSize(1) == k);
FAISS_ASSERT(outIndices.getSize(1) == k);
auto& mem = resources->getMemoryManagerCurrentDevice();
auto defaultStream = resources->getDefaultStreamCurrentDevice();
auto defaultStream = res->getDefaultStreamCurrentDevice();
// If we're quering against a 0 sized set, just return empty results
if (centroids.numElements() == 0) {
@@ -296,7 +295,7 @@ void runGeneralDistance(GpuResources* resources,
numCentroids,
dim,
sizeof(T),
mem.getSizeAvailable(),
res->getTempMemoryAvailableCurrentDevice(),
tileRows,
tileCols);
@@ -308,27 +307,27 @@ void runGeneralDistance(GpuResources* resources,
// Temporary output memory space we'll use
DeviceTensor<float, 2, true> distanceBuf1(
mem, {tileRows, tileCols}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, tileCols});
DeviceTensor<float, 2, true> distanceBuf2(
mem, {tileRows, tileCols}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, tileCols});
DeviceTensor<float, 2, true>* distanceBufs[2] =
{&distanceBuf1, &distanceBuf2};
DeviceTensor<float, 2, true> outDistanceBuf1(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<float, 2, true> outDistanceBuf2(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<float, 2, true>* outDistanceBufs[2] =
{&outDistanceBuf1, &outDistanceBuf2};
DeviceTensor<int, 2, true> outIndexBuf1(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<int, 2, true> outIndexBuf2(
mem, {tileRows, numColTiles * k}, defaultStream);
res, makeTempAlloc(AllocType::Other, defaultStream), {tileRows, numColTiles * k});
DeviceTensor<int, 2, true>* outIndexBufs[2] =
{&outIndexBuf1, &outIndexBuf2};
auto streams = resources->getAlternateStreamsCurrentDevice();
auto streams = res->getAlternateStreamsCurrentDevice();
streamWait(streams, {defaultStream});
int curStream = 0;
+6 -2
View File
@@ -31,9 +31,13 @@ inline bool isSQSupported(ScalarQuantizer::QuantizerType qtype) {
// Wrapper around the CPU ScalarQuantizer that allows storage of parameters in
// GPU memory
struct GpuScalarQuantizer : public ScalarQuantizer {
GpuScalarQuantizer(const ScalarQuantizer& sq)
GpuScalarQuantizer(GpuResources* res,
const ScalarQuantizer& sq)
: ScalarQuantizer(sq),
gpuTrained(DeviceTensor<float, 1, true>({(int) sq.trained.size()})) {
gpuTrained(DeviceTensor<float, 1, true>(
res,
makeDevAlloc(AllocType::Quantizer, 0),
{(int) sq.trained.size()})) {
HostTensor<float, 1, true>
cpuTrained((float*) sq.trained.data(), {(int) sq.trained.size()});
+13 -10
View File
@@ -31,11 +31,11 @@ IVFBase::IVFBase(GpuResources* resources,
metric_(metric),
metricArg_(metricArg),
quantizer_(quantizer),
dim_(quantizer->getDim()),
numLists_(quantizer->getSize()),
bytesPerVector_(bytesPerVector),
indicesOptions_(indicesOptions),
space_(space),
dim_(quantizer->getDim()),
numLists_(quantizer->getSize()),
maxListLength_(0) {
reset();
}
@@ -82,13 +82,18 @@ IVFBase::reset() {
deviceListLengths_.clear();
listOffsetToUserIndex_.clear();
auto info = AllocInfo(AllocType::IVFLists,
getCurrentDevice(),
space_,
resources_->getDefaultStreamCurrentDevice());
for (size_t i = 0; i < numLists_; ++i) {
deviceListData_.emplace_back(
std::unique_ptr<DeviceVector<unsigned char>>(
new DeviceVector<unsigned char>(space_)));
new DeviceVector<unsigned char>(resources_, info)));
deviceListIndices_.emplace_back(
std::unique_ptr<DeviceVector<unsigned char>>(
new DeviceVector<unsigned char>(space_)));
new DeviceVector<unsigned char>(resources_, info)));
listOffsetToUserIndex_.emplace_back(std::vector<long>());
}
@@ -149,8 +154,6 @@ IVFBase::updateDeviceListInfo_(cudaStream_t stream) {
void
IVFBase::updateDeviceListInfo_(const std::vector<int>& listIds,
cudaStream_t stream) {
auto& mem = resources_->getMemoryManagerCurrentDevice();
HostTensor<int, 1, true>
hostListsToUpdate({(int) listIds.size()});
HostTensor<int, 1, true>
@@ -173,13 +176,13 @@ IVFBase::updateDeviceListInfo_(const std::vector<int>& listIds,
// Copy the above update sets to the GPU
DeviceTensor<int, 1, true> listsToUpdate(
mem, hostListsToUpdate, stream);
resources_, makeTempAlloc(AllocType::Other, stream), hostListsToUpdate);
DeviceTensor<int, 1, true> newListLength(
mem, hostNewListLength, stream);
resources_, makeTempAlloc(AllocType::Other, stream), hostNewListLength);
DeviceTensor<void*, 1, true> newDataPointers(
mem, hostNewDataPointers, stream);
resources_, makeTempAlloc(AllocType::Other, stream), hostNewDataPointers);
DeviceTensor<void*, 1, true> newIndexPointers(
mem, hostNewIndexPointers, stream);
resources_, makeTempAlloc(AllocType::Other, stream), hostNewIndexPointers);
// Update all pointers to the lists on the device that may have
// changed
-1
View File
@@ -12,7 +12,6 @@
#include <faiss/gpu/GpuIndicesOptions.h>
#include <faiss/gpu/utils/DeviceVector.cuh>
#include <faiss/gpu/utils/DeviceTensor.cuh>
#include <faiss/gpu/utils/MemorySpace.h>
#include <memory>
#include <thrust/device_vector.h>
#include <vector>
+18 -18
View File
@@ -25,7 +25,7 @@
namespace faiss { namespace gpu {
IVFFlat::IVFFlat(GpuResources* resources,
IVFFlat::IVFFlat(GpuResources* res,
FlatIndex* quantizer,
faiss::MetricType metric,
float metricArg,
@@ -33,7 +33,7 @@ IVFFlat::IVFFlat(GpuResources* resources,
faiss::ScalarQuantizer* scalarQ,
IndicesOptions indicesOptions,
MemorySpace space) :
IVFBase(resources,
IVFBase(res,
metric,
metricArg,
quantizer,
@@ -42,7 +42,7 @@ IVFFlat::IVFFlat(GpuResources* resources,
indicesOptions,
space),
useResidual_(useResidual),
scalarQ_(scalarQ ? new GpuScalarQuantizer(*scalarQ) : nullptr) {
scalarQ_(scalarQ ? new GpuScalarQuantizer(res, *scalarQ) : nullptr) {
}
IVFFlat::~IVFFlat() {
@@ -106,25 +106,24 @@ IVFFlat::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
FAISS_ASSERT(vecs.getSize(0) == indices.getSize(0));
FAISS_ASSERT(vecs.getSize(1) == dim_);
auto& mem = resources_->getMemoryManagerCurrentDevice();
auto stream = resources_->getDefaultStreamCurrentDevice();
// Number of valid vectors that we actually add; we return this
int numAdded = 0;
DeviceTensor<float, 2, true>
listDistance2d(mem, {vecs.getSize(0), 1}, stream);
DeviceTensor<float, 2, true> listDistance2d(
resources_, makeTempAlloc(AllocType::Other, stream), {vecs.getSize(0), 1});
DeviceTensor<int, 2, true>
listIds2d(mem, {vecs.getSize(0), 1}, stream);
DeviceTensor<int, 2, true> listIds2d(
resources_, makeTempAlloc(AllocType::Other, stream), {vecs.getSize(0), 1});
auto listIds = listIds2d.view<1>({vecs.getSize(0)});
quantizer_->query(vecs, 1, metric_, metricArg_,
listDistance2d, listIds2d, false);
// Calculate residuals for these vectors, if needed
DeviceTensor<float, 2, true>
residuals(mem, {vecs.getSize(0), dim_}, stream);
DeviceTensor<float, 2, true> residuals(
resources_, makeTempAlloc(AllocType::Other, stream), {vecs.getSize(0), dim_});
if (useResidual_) {
quantizer_->computeResidual(vecs, listIds, residuals);
@@ -251,7 +250,8 @@ IVFFlat::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
// We similarly need to actually append the new vectors
{
DeviceTensor<int, 1, true> listOffset(mem, listOffsetHost, stream);
DeviceTensor<int, 1, true> listOffset(
resources_, makeTempAlloc(AllocType::Other, stream), listOffsetHost);
// Now, for each list to which a vector is being assigned, write it
runIVFFlatInvertedListAppend(listIds,
@@ -276,7 +276,6 @@ IVFFlat::query(Tensor<float, 2, true>& queries,
int k,
Tensor<float, 2, true>& outDistances,
Tensor<long, 2, true>& outIndices) {
auto& mem = resources_->getMemoryManagerCurrentDevice();
auto stream = resources_->getDefaultStreamCurrentDevice();
// These are caught at a higher level
@@ -290,10 +289,10 @@ IVFFlat::query(Tensor<float, 2, true>& queries,
FAISS_ASSERT(outIndices.getSize(0) == queries.getSize(0));
// Reserve space for the quantized information
DeviceTensor<float, 2, true>
coarseDistances(mem, {queries.getSize(0), nprobe}, stream);
DeviceTensor<int, 2, true>
coarseIndices(mem, {queries.getSize(0), nprobe}, stream);
DeviceTensor<float, 2, true> coarseDistances(
resources_, makeTempAlloc(AllocType::Other, stream), {queries.getSize(0), nprobe});
DeviceTensor<int, 2, true> coarseIndices(
resources_, makeTempAlloc(AllocType::Other, stream), {queries.getSize(0), nprobe});
// Find the `nprobe` closest lists; we can use int indices both
// internally and externally
@@ -305,8 +304,9 @@ IVFFlat::query(Tensor<float, 2, true>& queries,
coarseIndices,
false);
DeviceTensor<float, 3, true>
residualBase(mem, {queries.getSize(0), nprobe, dim_}, stream);
DeviceTensor<float, 3, true> residualBase(
resources_, makeTempAlloc(AllocType::Other, stream),
{queries.getSize(0), nprobe, dim_});
if (useResidual_) {
// Reconstruct vectors from the quantizer
+23 -15
View File
@@ -176,7 +176,8 @@ ivfFlatScan(Tensor<float, 2, true> queries,
}
void
runIVFFlatScanTile(Tensor<float, 2, true>& queries,
runIVFFlatScanTile(GpuResources* res,
Tensor<float, 2, true>& queries,
Tensor<int, 2, true>& listIds,
thrust::device_vector<void*>& listData,
thrust::device_vector<void*>& listIndices,
@@ -214,7 +215,8 @@ runIVFFlatScanTile(Tensor<float, 2, true>& queries,
// Calculate offset lengths, so we know where to write out
// intermediate results
runCalcListOffsets(listIds, listLengths, prefixSumOffsets, thrustMem, stream);
runCalcListOffsets(
res, listIds, listLengths, prefixSumOffsets, thrustMem, stream);
auto grid = dim3(listIds.getSize(1), listIds.getSize(0));
auto block = dim3(kWarpSize * kIVFFlatScanWarps);
@@ -383,21 +385,20 @@ runIVFFlatScan(Tensor<float, 2, true>& queries,
int nprobe = listIds.getSize(1);
auto& mem = res->getMemoryManagerCurrentDevice();
auto stream = res->getDefaultStreamCurrentDevice();
// Make a reservation for Thrust to do its dirty work (global memory
// cross-block reduction space); hopefully this is large enough.
DeviceTensor<char, 1, true> thrustMem1(
mem, {kThrustMemSize}, stream);
res, makeTempAlloc(AllocType::Other, stream), {kThrustMemSize});
DeviceTensor<char, 1, true> thrustMem2(
mem, {kThrustMemSize}, stream);
res, makeTempAlloc(AllocType::Other, stream), {kThrustMemSize});
DeviceTensor<char, 1, true>* thrustMem[2] =
{&thrustMem1, &thrustMem2};
// How much temporary storage is available?
// If possible, we'd like to fit within the space available.
size_t sizeAvailable = mem.getSizeAvailable();
size_t sizeAvailable = res->getTempMemoryAvailableCurrentDevice();
// We run two passes of heap selection
// This is the size of the first-level heap passes
@@ -431,9 +432,9 @@ runIVFFlatScan(Tensor<float, 2, true>& queries,
// Make sure there is space prior to the start which will be 0, and
// will handle the boundary condition without branches
DeviceTensor<int, 1, true> prefixSumOffsetSpace1(
mem, {queryTileSize * nprobe + 1}, stream);
res, makeTempAlloc(AllocType::Other, stream), {queryTileSize * nprobe + 1});
DeviceTensor<int, 1, true> prefixSumOffsetSpace2(
mem, {queryTileSize * nprobe + 1}, stream);
res, makeTempAlloc(AllocType::Other, stream), {queryTileSize * nprobe + 1});
DeviceTensor<int, 2, true> prefixSumOffsets1(
prefixSumOffsetSpace1[1].data(),
@@ -456,23 +457,29 @@ runIVFFlatScan(Tensor<float, 2, true>& queries,
stream));
DeviceTensor<float, 1, true> allDistances1(
mem, {queryTileSize * nprobe * maxListLength}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe * maxListLength});
DeviceTensor<float, 1, true> allDistances2(
mem, {queryTileSize * nprobe * maxListLength}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe * maxListLength});
DeviceTensor<float, 1, true>* allDistances[2] =
{&allDistances1, &allDistances2};
DeviceTensor<float, 3, true> heapDistances1(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<float, 3, true> heapDistances2(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<float, 3, true>* heapDistances[2] =
{&heapDistances1, &heapDistances2};
DeviceTensor<int, 3, true> heapIndices1(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<int, 3, true> heapIndices2(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<int, 3, true>* heapIndices[2] =
{&heapIndices1, &heapIndices2};
@@ -505,7 +512,8 @@ runIVFFlatScan(Tensor<float, 2, true>& queries,
auto outIndicesView =
outIndices.narrowOutermost(query, numQueriesInTile);
runIVFFlatScanTile(queryView,
runIVFFlatScanTile(res,
queryView,
listIdsView,
listData,
listIndices,
+109 -66
View File
@@ -52,8 +52,8 @@ IVFPQ::IVFPQ(GpuResources* resources,
bitsPerSubQuantizer_(bitsPerSubQuantizer),
numSubQuantizerCodes_(utils::pow2(bitsPerSubQuantizer_)),
dimPerSubQuantizer_(dim_ / numSubQuantizers),
precomputedCodes_(false),
useFloat16LookupTables_(useFloat16LookupTables) {
useFloat16LookupTables_(useFloat16LookupTables),
precomputedCodes_(false) {
FAISS_ASSERT(pqCentroidData);
FAISS_ASSERT(bitsPerSubQuantizer_ <= 8);
@@ -111,8 +111,8 @@ IVFPQ::setPrecomputedCodes(bool enable) {
precomputeCodes_();
} else {
// Clear out old precomputed code data
precomputedCode_ = std::move(DeviceTensor<float, 3, true>());
precomputedCodeHalf_ = std::move(DeviceTensor<half, 3, true>());
precomputedCode_ = DeviceTensor<float, 3, true>();
precomputedCodeHalf_ = DeviceTensor<half, 3, true>();
}
}
}
@@ -123,16 +123,19 @@ IVFPQ::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
FAISS_ASSERT(vecs.getSize(0) == indices.getSize(0));
FAISS_ASSERT(vecs.getSize(1) == dim_);
auto& mem = resources_->getMemoryManagerCurrentDevice();
auto stream = resources_->getDefaultStreamCurrentDevice();
// Number of valid vectors that we actually add; we return this
int numAdded = 0;
// We don't actually need this
DeviceTensor<float, 2, true> listDistance(mem, {vecs.getSize(0), 1}, stream);
DeviceTensor<float, 2, true> listDistance(
resources_, makeTempAlloc(AllocType::Other, stream),
{vecs.getSize(0), 1});
// We use this
DeviceTensor<int, 2, true> listIds2d(mem, {vecs.getSize(0), 1}, stream);
DeviceTensor<int, 2, true> listIds2d(
resources_, makeTempAlloc(AllocType::Other, stream),
{vecs.getSize(0), 1});
auto listIds = listIds2d.view<1>({vecs.getSize(0)});
quantizer_->query(vecs,
@@ -151,7 +154,8 @@ IVFPQ::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
// Calculate the residual for each closest centroid
DeviceTensor<float, 2, true> residuals(
mem, {vecs.getSize(0), vecs.getSize(1)}, stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{vecs.getSize(0), vecs.getSize(1)});
if (quantizer_->getUseFloat16()) {
auto& coarseCentroids = quantizer_->getVectorsFloat16Ref();
@@ -169,9 +173,8 @@ IVFPQ::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
{residuals.getSize(0), numSubQuantizers_, dimPerSubQuantizer_});
DeviceTensor<float, 3, true> residualsTranspose(
mem,
{numSubQuantizers_, residuals.getSize(0), dimPerSubQuantizer_},
stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{numSubQuantizers_, residuals.getSize(0), dimPerSubQuantizer_});
runTransposeAny(residualsView, 0, 1, residualsTranspose, stream);
@@ -186,9 +189,11 @@ IVFPQ::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
// => (numSubQuantizer x vec x 1)
DeviceTensor<float, 3, true> closestSubQDistance(
mem, {numSubQuantizers_, residuals.getSize(0), 1}, stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{numSubQuantizers_, residuals.getSize(0), 1});
DeviceTensor<int, 3, true> closestSubQIndex(
mem, {numSubQuantizers_, residuals.getSize(0), 1}, stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{numSubQuantizers_, residuals.getSize(0), 1});
for (int subQ = 0; subQ < numSubQuantizers_; ++subQ) {
auto closestSubQDistanceView = closestSubQDistance[subQ].view();
@@ -217,7 +222,8 @@ IVFPQ::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
// Transpose this for easy use
DeviceTensor<int, 2, true> encodings(
mem, {residuals.getSize(0), numSubQuantizers_}, stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{residuals.getSize(0), numSubQuantizers_});
runTransposeAny(closestSubQIndexView, 0, 1, encodings, stream);
@@ -337,7 +343,9 @@ IVFPQ::classifyAndAddVectors(Tensor<float, 2, true>& vecs,
// We similarly need to actually append the new encoded vectors
{
DeviceTensor<int, 1, true> listOffset(mem, listOffsetHost, stream);
DeviceTensor<int, 1, true> listOffset(
resources_, makeTempAlloc(AllocType::Other, stream),
listOffsetHost);
// This kernel will handle appending each encoded vector + index to
// the appropriate list
@@ -409,6 +417,8 @@ IVFPQ::addCodeVectorsFromCpu(int listId,
void
IVFPQ::setPQCentroids_(float* data) {
auto stream = resources_->getDefaultStreamCurrentDevice();
size_t pqSize =
numSubQuantizers_ * numSubQuantizerCodes_ * dimPerSubQuantizer_;
@@ -420,23 +430,34 @@ IVFPQ::setPQCentroids_(float* data) {
HostTensor<float, 3, true> pqHost(
hostMemory.data(),
{numSubQuantizers_, numSubQuantizerCodes_, dimPerSubQuantizer_});
DeviceTensor<float, 3, true> pqDevice(
pqHost,
resources_->getDefaultStreamCurrentDevice());
DeviceTensor<float, 3, true> pqDeviceTranspose(
resources_,
makeDevAlloc(AllocType::Quantizer, stream),
{numSubQuantizers_, dimPerSubQuantizer_, numSubQuantizerCodes_});
runTransposeAny(pqDevice, 1, 2, pqDeviceTranspose,
resources_->getDefaultStreamCurrentDevice());
{
// Only needed for the duration of the transposition
DeviceTensor<float, 3, true> pqDevice(
resources_,
makeTempAlloc(AllocType::Quantizer, stream),
pqHost);
runTransposeAny(pqDevice, 1, 2, pqDeviceTranspose,
stream);
}
pqCentroidsInnermostCode_ = std::move(pqDeviceTranspose);
// Also maintain the PQ centroids in the form
// (sub q)(code id)(sub dim)
DeviceTensor<float, 3, true> pqCentroidsMiddleCode(
resources_,
makeDevAlloc(AllocType::Quantizer, stream),
{numSubQuantizers_, numSubQuantizerCodes_, dimPerSubQuantizer_});
runTransposeAny(pqCentroidsInnermostCode_, 1, 2, pqCentroidsMiddleCode,
resources_->getDefaultStreamCurrentDevice());
stream);
pqCentroidsMiddleCode_ = std::move(pqCentroidsMiddleCode);
}
@@ -446,6 +467,8 @@ void
IVFPQ::precomputeCodesT_() {
FAISS_ASSERT(metric_ == MetricType::METRIC_L2);
auto stream = resources_->getDefaultStreamCurrentDevice();
//
// d = || x - y_C ||^2 + || y_R ||^2 + 2 * (y_C|y_R) - 2 * (x|y_R)
// --------------- --------------------------- -------
@@ -455,18 +478,6 @@ IVFPQ::precomputeCodesT_() {
// Terms 1 and 3 are available only at query time. We compute term 2
// here.
// Compute ||y_R||^2 by treating
// (sub q)(code id)(sub dim) as (sub q * code id)(sub dim)
auto pqCentroidsMiddleCodeView =
pqCentroidsMiddleCode_.view<2>(
{numSubQuantizers_ * numSubQuantizerCodes_, dimPerSubQuantizer_});
DeviceTensor<float, 1, true> subQuantizerNorms(
{numSubQuantizers_ * numSubQuantizerCodes_});
runL2Norm(pqCentroidsMiddleCodeView, true,
subQuantizerNorms, true,
resources_->getDefaultStreamCurrentDevice());
// Compute 2 * (y_C|y_R) via batch matrix multiplication
// batch size (sub q) x {(centroid id)(sub dim) x (code id)(sub dim)'}
// => (sub q) x {(centroid id)(code id)}
@@ -477,30 +488,43 @@ IVFPQ::precomputeCodesT_() {
// Transpose (centroid id)(sub q)(sub dim) to
// (sub q)(centroid id)(sub dim)
auto& coarseCentroids = quantizer_->template getVectorsRef<CentroidT>();
auto centroidView = coarseCentroids.template view<3>(
{coarseCentroids.getSize(0), numSubQuantizers_, dimPerSubQuantizer_});
DeviceTensor<CentroidT, 3, true> centroidsTransposed(
{numSubQuantizers_, coarseCentroids.getSize(0), dimPerSubQuantizer_});
runTransposeAny(centroidView, 0, 1, centroidsTransposed,
resources_->getDefaultStreamCurrentDevice());
// Create the coarse PQ product
DeviceTensor<float, 3, true> coarsePQProduct(
resources_,
makeTempAlloc(AllocType::QuantizerPrecomputedCodes, stream),
{numSubQuantizers_, coarseCentroids.getSize(0), numSubQuantizerCodes_});
runIteratedMatrixMult(coarsePQProduct, false,
centroidsTransposed, false,
pqCentroidsMiddleCode_, true,
2.0f, 0.0f,
resources_->getBlasHandleCurrentDevice(),
resources_->getDefaultStreamCurrentDevice());
{
auto centroidView = coarseCentroids.template view<3>(
{coarseCentroids.getSize(0), numSubQuantizers_, dimPerSubQuantizer_});
// This is only needed temporarily
DeviceTensor<CentroidT, 3, true> centroidsTransposed(
resources_,
makeTempAlloc(AllocType::QuantizerPrecomputedCodes, stream),
{numSubQuantizers_, coarseCentroids.getSize(0), dimPerSubQuantizer_});
runTransposeAny(centroidView, 0, 1, centroidsTransposed,
stream);
runIteratedMatrixMult(coarsePQProduct, false,
centroidsTransposed, false,
pqCentroidsMiddleCode_, true,
2.0f, 0.0f,
resources_->getBlasHandleCurrentDevice(),
stream);
}
// Transpose (sub q)(centroid id)(code id) to
// (centroid id)(sub q)(code id)
// This will become our precomputed code output
DeviceTensor<float, 3, true> coarsePQProductTransposed(
resources_,
makeDevAlloc(AllocType::QuantizerPrecomputedCodes, stream),
{coarseCentroids.getSize(0), numSubQuantizers_, numSubQuantizerCodes_});
runTransposeAny(coarsePQProduct, 0, 1, coarsePQProductTransposed,
resources_->getDefaultStreamCurrentDevice());
stream);
// View (centroid id)(sub q)(code id) as
// (centroid id)(sub q * code id)
@@ -510,16 +534,35 @@ IVFPQ::precomputeCodesT_() {
// Sum || y_R ||^2 + 2 * (y_C|y_R)
// i.e., add norms (sub q * code id)
// along columns of inner product (centroid id)(sub q * code id)
runSumAlongColumns(subQuantizerNorms, coarsePQProductTransposedView,
resources_->getDefaultStreamCurrentDevice());
{
// Compute ||y_R||^2 by treating
// (sub q)(code id)(sub dim) as (sub q * code id)(sub dim)
auto pqCentroidsMiddleCodeView =
pqCentroidsMiddleCode_.view<2>(
{numSubQuantizers_ * numSubQuantizerCodes_, dimPerSubQuantizer_});
DeviceTensor<float, 1, true> subQuantizerNorms(
resources_,
makeTempAlloc(AllocType::QuantizerPrecomputedCodes, stream),
{numSubQuantizers_ * numSubQuantizerCodes_});
runL2Norm(pqCentroidsMiddleCodeView, true,
subQuantizerNorms, true,
stream);
runSumAlongColumns(subQuantizerNorms, coarsePQProductTransposedView,
stream);
}
// We added into the view, so `coarsePQProductTransposed` is now our
// precomputed term 2.
if (useFloat16LookupTables_) {
precomputedCodeHalf_ =
convertTensor<float, half, 3>(resources_,
resources_->getDefaultStreamCurrentDevice(),
coarsePQProductTransposed);
DeviceTensor<half, 3, true>(
resources_,
makeDevAlloc(AllocType::QuantizerPrecomputedCodes, stream),
{coarseCentroids.getSize(0), numSubQuantizers_, numSubQuantizerCodes_});
convertTensor(stream, coarsePQProductTransposed, precomputedCodeHalf_);
} else {
precomputedCode_ = std::move(coarsePQProductTransposed);
}
@@ -544,7 +587,6 @@ IVFPQ::query(Tensor<float, 2, true>& queries,
FAISS_ASSERT(nprobe <= GPU_MAX_SELECTION_K);
FAISS_ASSERT(k <= GPU_MAX_SELECTION_K);
auto& mem = resources_->getMemoryManagerCurrentDevice();
auto stream = resources_->getDefaultStreamCurrentDevice();
nprobe = std::min(nprobe, quantizer_->getSize());
@@ -554,9 +596,13 @@ IVFPQ::query(Tensor<float, 2, true>& queries,
// Reserve space for the closest coarse centroids
DeviceTensor<float, 2, true>
coarseDistances(mem, {queries.getSize(0), nprobe}, stream);
coarseDistances(
resources_, makeTempAlloc(AllocType::Other, stream),
{queries.getSize(0), nprobe});
DeviceTensor<int, 2, true>
coarseIndices(mem, {queries.getSize(0), nprobe}, stream);
coarseIndices(
resources_, makeTempAlloc(AllocType::Other, stream),
{queries.getSize(0), nprobe});
// Find the `nprobe` closest coarse centroids; we can use int
// indices both internally and externally
@@ -628,7 +674,6 @@ IVFPQ::runPQPrecomputedCodes_(
Tensor<long, 2, true>& outIndices) {
FAISS_ASSERT(metric_ == MetricType::METRIC_L2);
auto& mem = resources_->getMemoryManagerCurrentDevice();
auto stream = resources_->getDefaultStreamCurrentDevice();
// Compute precomputed code term 3, - 2 * (x|y_R)
@@ -636,9 +681,8 @@ IVFPQ::runPQPrecomputedCodes_(
// {sub q} x {(query id)(sub dim) * (code id)(sub dim)'} =>
// {sub q} x {(query id)(code id)}
DeviceTensor<float, 3, true> term3Transposed(
mem,
{queries.getSize(0), numSubQuantizers_, numSubQuantizerCodes_},
stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{queries.getSize(0), numSubQuantizers_, numSubQuantizerCodes_});
// These allocations within are only temporary, so release them when
// we're done to maximize free space
@@ -646,15 +690,13 @@ IVFPQ::runPQPrecomputedCodes_(
auto querySubQuantizerView = queries.view<3>(
{queries.getSize(0), numSubQuantizers_, dimPerSubQuantizer_});
DeviceTensor<float, 3, true> queriesTransposed(
mem,
{numSubQuantizers_, queries.getSize(0), dimPerSubQuantizer_},
stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{numSubQuantizers_, queries.getSize(0), dimPerSubQuantizer_});
runTransposeAny(querySubQuantizerView, 0, 1, queriesTransposed, stream);
DeviceTensor<float, 3, true> term3(
mem,
{numSubQuantizers_, queries.getSize(0), numSubQuantizerCodes_},
stream);
resources_, makeTempAlloc(AllocType::Other, stream),
{numSubQuantizers_, queries.getSize(0), numSubQuantizerCodes_});
runIteratedMatrixMult(term3, false,
queriesTransposed, false,
@@ -672,7 +714,8 @@ IVFPQ::runPQPrecomputedCodes_(
if (useFloat16LookupTables_) {
term3Half =
convertTensor<float, half, 3>(resources_, stream, term3Transposed);
convertTensorTemporary<float, half, 3>(
resources_, stream, term3Transposed);
term2 = NoTypeTensor<3, true>(precomputedCodeHalf_);
term3 = NoTypeTensor<3, true>(term3Half);
+6 -3
View File
@@ -38,7 +38,8 @@ getResultLengths(Tensor<int, 2, true> topQueryToCentroid,
length[queryId][listId] = (centroidId != -1) ? listLengths[centroidId] : 0;
}
void runCalcListOffsets(Tensor<int, 2, true>& topQueryToCentroid,
void runCalcListOffsets(GpuResources* res,
Tensor<int, 2, true>& topQueryToCentroid,
thrust::device_vector<int>& listLengths,
Tensor<int, 2, true>& prefixSumOffsets,
Tensor<char, 1, true>& thrustMem,
@@ -64,8 +65,10 @@ void runCalcListOffsets(Tensor<int, 2, true>& topQueryToCentroid,
// Prefix sum of the indices, so we know where the intermediate
// results should be maintained
// Thrust wants a place for its temporary allocations, so provide
// one, so it won't call cudaMalloc/Free
GpuResourcesThrustAllocator alloc(thrustMem.data(),
// one, so it won't call cudaMalloc/Free if we size it sufficiently
GpuResourcesThrustAllocator alloc(res,
stream,
thrustMem.data(),
thrustMem.getSizeInBytes());
thrust::inclusive_scan(thrust::cuda::par(alloc).on(stream),
+4 -1
View File
@@ -16,9 +16,12 @@
// post-processing and k-selecting the results
namespace faiss { namespace gpu {
class GpuResources;
/// Function for multi-pass scanning that collects the length of
/// intermediate results for all (query, probe) pair
void runCalcListOffsets(Tensor<int, 2, true>& topQueryToCentroid,
void runCalcListOffsets(GpuResources* res,
Tensor<int, 2, true>& topQueryToCentroid,
thrust::device_vector<int>& listLengths,
Tensor<int, 2, true>& prefixSumOffsets,
Tensor<char, 1, true>& thrustMem,
+2 -2
View File
@@ -241,7 +241,7 @@ void runL2Norm(Tensor<T, 2, true, IndexType>& input,
auto dim = inputV.getSize(1);
bool normLoop = dim > maxThreads;
auto numThreads = min(dim, maxThreads);
auto numThreads = std::min(dim, maxThreads);
auto grid = dim3(utils::divUp(inputV.getSize(0), rowTileSize));
auto block = dim3(numThreads);
@@ -254,7 +254,7 @@ void runL2Norm(Tensor<T, 2, true, IndexType>& input,
auto dim = input.getSize(1);
bool normLoop = dim > maxThreads;
auto numThreads = min(dim, maxThreads);
auto numThreads = std::min(dim, maxThreads);
auto grid = dim3(utils::divUp(input.getSize(0), rowTileSize));
auto block = dim3(numThreads);
+20 -25
View File
@@ -320,18 +320,17 @@ runPQCodeDistancesMM(Tensor<float, 3, true>& pqCentroids,
Tensor<int, 2, true>& topQueryToCentroid,
NoTypeTensor<4, true>& outCodeDistances,
bool useFloat16Lookup,
DeviceMemory& mem,
GpuResources* res,
cublasHandle_t handle,
cudaStream_t stream) {
// Calculate (q - c) residual vector
// (sub q)(query id)(centroid id)(sub dim)
DeviceTensor<float, 4, true> residual(
mem,
res, makeTempAlloc(AllocType::Other, stream),
{pqCentroids.getSize(0),
topQueryToCentroid.getSize(0),
topQueryToCentroid.getSize(1),
pqCentroids.getSize(1)},
stream);
topQueryToCentroid.getSize(0),
topQueryToCentroid.getSize(1),
pqCentroids.getSize(1)});
runResidualVector(pqCentroids, queries,
coarseCentroids, topQueryToCentroid,
@@ -339,11 +338,10 @@ runPQCodeDistancesMM(Tensor<float, 3, true>& pqCentroids,
// Calculate ||q - c||^2
DeviceTensor<float, 1, true> residualNorms(
mem,
res, makeTempAlloc(AllocType::Other, stream),
{pqCentroids.getSize(0) *
topQueryToCentroid.getSize(0) *
topQueryToCentroid.getSize(1)},
stream);
topQueryToCentroid.getSize(0) *
topQueryToCentroid.getSize(1)});
auto residualView2 = residual.view<2>(
{pqCentroids.getSize(0) *
@@ -362,11 +360,10 @@ runPQCodeDistancesMM(Tensor<float, 3, true>& pqCentroids,
pqCentroids.getSize(1)});
DeviceTensor<float, 3, true> residualDistance(
mem,
res, makeTempAlloc(AllocType::Other, stream),
{pqCentroids.getSize(0),
topQueryToCentroid.getSize(0) * topQueryToCentroid.getSize(1),
pqCentroids.getSize(2)},
stream);
topQueryToCentroid.getSize(0) * topQueryToCentroid.getSize(1),
pqCentroids.getSize(2)});
runIteratedMatrixMult(residualDistance, false,
residualView3, false,
@@ -389,11 +386,11 @@ runPQCodeDistancesMM(Tensor<float, 3, true>& pqCentroids,
if (useFloat16Lookup) {
outCodeDistancesFloatMem = DeviceTensor<float, 4, true>(
mem, {outCodeDistances.getSize(0),
outCodeDistances.getSize(1),
outCodeDistances.getSize(2),
outCodeDistances.getSize(3)},
stream);
res, makeTempAlloc(AllocType::Other, stream),
{outCodeDistances.getSize(0),
outCodeDistances.getSize(1),
outCodeDistances.getSize(2),
outCodeDistances.getSize(3)});
outCodeDistancesF = outCodeDistancesFloatMem;
} else {
@@ -413,9 +410,8 @@ runPQCodeDistancesMM(Tensor<float, 3, true>& pqCentroids,
// (sub q)(sub dim)(code) is pqCentroids
// transpose to (sub q)(code)(sub dim)
DeviceTensor<float, 3, true> pqCentroidsTranspose(
mem,
{pqCentroids.getSize(0), pqCentroids.getSize(2), pqCentroids.getSize(1)},
stream);
res, makeTempAlloc(AllocType::Other, stream),
{pqCentroids.getSize(0), pqCentroids.getSize(2), pqCentroids.getSize(1)});
runTransposeAny(pqCentroids, 1, 2, pqCentroidsTranspose, stream);
@@ -424,9 +420,8 @@ runPQCodeDistancesMM(Tensor<float, 3, true>& pqCentroids,
pqCentroids.getSize(1)});
DeviceTensor<float, 1, true> pqCentroidsNorm(
mem,
{pqCentroids.getSize(0) * pqCentroids.getSize(2)},
stream);
res, makeTempAlloc(AllocType::Other, stream),
{pqCentroids.getSize(0) * pqCentroids.getSize(2)});
runL2Norm(pqCentroidsTransposeView, true, pqCentroidsNorm, true, stream);
+1 -1
View File
@@ -37,7 +37,7 @@ void runPQCodeDistancesMM(Tensor<float, 3, true>& pqCentroids,
Tensor<int, 2, true>& topQueryToCentroid,
NoTypeTensor<4, true>& outCodeDistances,
bool useFloat16Lookup,
DeviceMemory& mem,
GpuResources* res,
cublasHandle_t handle,
cudaStream_t stream);
+30 -17
View File
@@ -219,7 +219,8 @@ pqScanNoPrecomputedMultiPass(Tensor<float, 2, true> queries,
template <typename CentroidT>
void
runMultiPassTile(Tensor<float, 2, true>& queries,
runMultiPassTile(GpuResources* res,
Tensor<float, 2, true>& queries,
Tensor<CentroidT, 2, true>& centroids,
Tensor<float, 3, true>& pqCentroidsInnermostCode,
NoTypeTensor<4, true>& codeDistances,
@@ -250,7 +251,7 @@ runMultiPassTile(Tensor<float, 2, true>& queries,
// Calculate offset lengths, so we know where to write out
// intermediate results
runCalcListOffsets(topQueryToCentroid, listLengths, prefixSumOffsets,
runCalcListOffsets(res, topQueryToCentroid, listLengths, prefixSumOffsets,
thrustMem, stream);
// Calculate residual code distances, since this is without
@@ -419,21 +420,22 @@ runPQScanMultiPassNoPrecomputed(Tensor<float, 2, true>& queries,
int nprobe = topQueryToCentroid.getSize(1);
auto& mem = res->getMemoryManagerCurrentDevice();
auto stream = res->getDefaultStreamCurrentDevice();
// Make a reservation for Thrust to do its dirty work (global memory
// cross-block reduction space); hopefully this is large enough.
DeviceTensor<char, 1, true> thrustMem1(
mem, {kThrustMemSize}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{kThrustMemSize});
DeviceTensor<char, 1, true> thrustMem2(
mem, {kThrustMemSize}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{kThrustMemSize});
DeviceTensor<char, 1, true>* thrustMem[2] =
{&thrustMem1, &thrustMem2};
// How much temporary storage is available?
// If possible, we'd like to fit within the space available.
size_t sizeAvailable = mem.getSizeAvailable();
size_t sizeAvailable = res->getTempMemoryAvailableCurrentDevice();
// We run two passes of heap selection
// This is the size of the first-level heap passes
@@ -469,9 +471,11 @@ runPQScanMultiPassNoPrecomputed(Tensor<float, 2, true>& queries,
// Make sure there is space prior to the start which will be 0, and
// will handle the boundary condition without branches
DeviceTensor<int, 1, true> prefixSumOffsetSpace1(
mem, {queryTileSize * nprobe + 1}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe + 1});
DeviceTensor<int, 1, true> prefixSumOffsetSpace2(
mem, {queryTileSize * nprobe + 1}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe + 1});
DeviceTensor<int, 2, true> prefixSumOffsets1(
prefixSumOffsetSpace1[1].data(),
@@ -500,14 +504,16 @@ runPQScanMultiPassNoPrecomputed(Tensor<float, 2, true>& queries,
codeDistanceTypeSize;
DeviceTensor<char, 1, true> codeDistances1Mem(
mem, {totalCodeDistancesSize}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{totalCodeDistancesSize});
NoTypeTensor<4, true> codeDistances1(
codeDistances1Mem.data(),
codeDistanceTypeSize,
{queryTileSize, nprobe, numSubQuantizers, numSubQuantizerCodes});
DeviceTensor<char, 1, true> codeDistances2Mem(
mem, {totalCodeDistancesSize}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{totalCodeDistancesSize});
NoTypeTensor<4, true> codeDistances2(
codeDistances2Mem.data(),
codeDistanceTypeSize,
@@ -517,23 +523,29 @@ runPQScanMultiPassNoPrecomputed(Tensor<float, 2, true>& queries,
{&codeDistances1, &codeDistances2};
DeviceTensor<float, 1, true> allDistances1(
mem, {queryTileSize * nprobe * maxListLength}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe * maxListLength});
DeviceTensor<float, 1, true> allDistances2(
mem, {queryTileSize * nprobe * maxListLength}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe * maxListLength});
DeviceTensor<float, 1, true>* allDistances[2] =
{&allDistances1, &allDistances2};
DeviceTensor<float, 3, true> heapDistances1(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<float, 3, true> heapDistances2(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<float, 3, true>* heapDistances[2] =
{&heapDistances1, &heapDistances2};
DeviceTensor<int, 3, true> heapIndices1(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<int, 3, true> heapIndices2(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<int, 3, true>* heapIndices[2] =
{&heapIndices1, &heapIndices2};
@@ -566,7 +578,8 @@ runPQScanMultiPassNoPrecomputed(Tensor<float, 2, true>& queries,
auto outIndicesView =
outIndices.narrowOutermost(query, numQueriesInTile);
runMultiPassTile(queryView,
runMultiPassTile(res,
queryView,
centroids,
pqCentroidsInnermostCode,
codeDistancesView,
+1 -1
View File
@@ -19,7 +19,7 @@ class GpuResources;
/// For no precomputed codes, is this a supported number of dimensions
/// per subquantizer?
bool isSupportedNoPrecomputedSubDimSize(int dims);
inline bool isSupportedNoPrecomputedSubDimSize(int dims);
template <typename CentroidT>
void runPQScanMultiPassNoPrecomputed(Tensor<float, 2, true>& queries,
+26 -15
View File
@@ -214,7 +214,8 @@ pqScanPrecomputedMultiPass(Tensor<float, 2, true> queries,
}
void
runMultiPassTile(Tensor<float, 2, true>& queries,
runMultiPassTile(GpuResources* res,
Tensor<float, 2, true>& queries,
Tensor<float, 2, true>& precompTerm1,
NoTypeTensor<3, true>& precompTerm2,
NoTypeTensor<3, true>& precompTerm3,
@@ -238,7 +239,7 @@ runMultiPassTile(Tensor<float, 2, true>& queries,
cudaStream_t stream) {
// Calculate offset lengths, so we know where to write out
// intermediate results
runCalcListOffsets(topQueryToCentroid, listLengths, prefixSumOffsets,
runCalcListOffsets(res, topQueryToCentroid, listLengths, prefixSumOffsets,
thrustMem, stream);
// Convert all codes to a distance, and write out (distance,
@@ -398,21 +399,22 @@ void runPQScanMultiPassPrecomputed(Tensor<float, 2, true>& queries,
int nprobe = topQueryToCentroid.getSize(1);
auto& mem = res->getMemoryManagerCurrentDevice();
auto stream = res->getDefaultStreamCurrentDevice();
// Make a reservation for Thrust to do its dirty work (global memory
// cross-block reduction space); hopefully this is large enough.
DeviceTensor<char, 1, true> thrustMem1(
mem, {kThrustMemSize}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{kThrustMemSize});
DeviceTensor<char, 1, true> thrustMem2(
mem, {kThrustMemSize}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{kThrustMemSize});
DeviceTensor<char, 1, true>* thrustMem[2] =
{&thrustMem1, &thrustMem2};
// How much temporary storage is available?
// If possible, we'd like to fit within the space available.
size_t sizeAvailable = mem.getSizeAvailable();
size_t sizeAvailable = res->getTempMemoryAvailableCurrentDevice();
// We run two passes of heap selection
// This is the size of the first-level heap passes
@@ -446,9 +448,11 @@ void runPQScanMultiPassPrecomputed(Tensor<float, 2, true>& queries,
// Make sure there is space prior to the start which will be 0, and
// will handle the boundary condition without branches
DeviceTensor<int, 1, true> prefixSumOffsetSpace1(
mem, {queryTileSize * nprobe + 1}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe + 1});
DeviceTensor<int, 1, true> prefixSumOffsetSpace2(
mem, {queryTileSize * nprobe + 1}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe + 1});
DeviceTensor<int, 2, true> prefixSumOffsets1(
prefixSumOffsetSpace1[1].data(),
@@ -471,23 +475,29 @@ void runPQScanMultiPassPrecomputed(Tensor<float, 2, true>& queries,
stream));
DeviceTensor<float, 1, true> allDistances1(
mem, {queryTileSize * nprobe * maxListLength}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe * maxListLength});
DeviceTensor<float, 1, true> allDistances2(
mem, {queryTileSize * nprobe * maxListLength}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize * nprobe * maxListLength});
DeviceTensor<float, 1, true>* allDistances[2] =
{&allDistances1, &allDistances2};
DeviceTensor<float, 3, true> heapDistances1(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<float, 3, true> heapDistances2(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<float, 3, true>* heapDistances[2] =
{&heapDistances1, &heapDistances2};
DeviceTensor<int, 3, true> heapIndices1(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<int, 3, true> heapIndices2(
mem, {queryTileSize, pass2Chunks, k}, stream);
res, makeTempAlloc(AllocType::Other, stream),
{queryTileSize, pass2Chunks, k});
DeviceTensor<int, 3, true>* heapIndices[2] =
{&heapIndices1, &heapIndices2};
@@ -522,7 +532,8 @@ void runPQScanMultiPassPrecomputed(Tensor<float, 2, true>& queries,
auto outIndicesView =
outIndices.narrowOutermost(query, numQueriesInTile);
runMultiPassTile(queryView,
runMultiPassTile(res,
queryView,
term1View,
precompTerm2,
term3View,
+1 -1
View File
@@ -13,7 +13,7 @@ namespace faiss { namespace gpu {
template <typename GpuIndex>
IndexWrapper<GpuIndex>::IndexWrapper(
int numGpus,
std::function<std::unique_ptr<GpuIndex>(GpuResources*, int)> init) {
std::function<std::unique_ptr<GpuIndex>(GpuResourcesProvider*, int)> init) {
FAISS_ASSERT(numGpus <= faiss::gpu::getNumDevices());
for (int i = 0; i < numGpus; ++i) {
auto res = std::unique_ptr<faiss::gpu::StandardGpuResources>(
+1 -1
View File
@@ -27,7 +27,7 @@ struct IndexWrapper {
IndexWrapper(
int numGpus,
std::function<std::unique_ptr<GpuIndex>(GpuResources*, int)> init);
std::function<std::unique_ptr<GpuIndex>(GpuResourcesProvider*, int)> init);
faiss::Index* getIndex();
void runOnIndices(std::function<void(GpuIndex*)> f);
+1 -1
View File
@@ -53,7 +53,7 @@ int main(int argc, char** argv) {
printf("transposed storage %s\n", FLAGS_transposed ? "enabled" : "disabled");
printf("verbose %s\n", FLAGS_verbose ? "enabled" : "disabled");
auto initFn = [](faiss::gpu::GpuResources* res, int dev) ->
auto initFn = [](faiss::gpu::GpuResourcesProvider* res, int dev) ->
std::unique_ptr<faiss::gpu::GpuIndexFlat> {
if (FLAGS_pinned_mem >= 0) {
((faiss::gpu::StandardGpuResources*) res)->setPinnedMemory(
+1 -1
View File
@@ -68,7 +68,7 @@ int main(int argc, char** argv) {
// Convert to GPU index
printf("Copying index to %d GPU(s)...\n", FLAGS_num_gpus);
auto initFn = [&index](faiss::gpu::GpuResources* res, int dev) ->
auto initFn = [&index](faiss::gpu::GpuResourcesProvider* res, int dev) ->
std::unique_ptr<faiss::gpu::GpuIndexFlat> {
((faiss::gpu::StandardGpuResources*) res)->setPinnedMemory(
FLAGS_pinned_mem);
+1 -3
View File
@@ -63,7 +63,7 @@ int main(int argc, char** argv) {
// Convert to GPU index
printf("Copying index to %d GPU(s)...\n", FLAGS_num_gpus);
auto initFn = [&index](faiss::gpu::GpuResources* res, int dev) ->
auto initFn = [&index](faiss::gpu::GpuResourcesProvider* res, int dev) ->
std::unique_ptr<faiss::gpu::GpuIndexIVFFlat> {
GpuIndexIVFFlatConfig config;
config.device = dev;
@@ -139,8 +139,6 @@ int main(int argc, char** argv) {
"", true, FLAGS_diff, false);
CUDA_VERIFY(cudaDeviceSynchronize());
// printf("\ncudaMalloc usage %zd\n",
// resources.getMemoryManager().getHighWaterCudaMalloc());
return 0;
}
+1 -3
View File
@@ -76,7 +76,7 @@ int main(int argc, char** argv) {
auto useFloat16Lookup = FLAGS_float16_lookup;
auto initFn = [precomp, indicesOpt, useFloat16Lookup, &index]
(faiss::gpu::GpuResources* res, int dev) ->
(faiss::gpu::GpuResourcesProvider* res, int dev) ->
std::unique_ptr<faiss::gpu::GpuIndexIVFPQ> {
faiss::gpu::GpuIndexIVFPQConfig config;
@@ -150,8 +150,6 @@ int main(int argc, char** argv) {
"", true, FLAGS_diff, false);
CUDA_VERIFY(cudaDeviceSynchronize());
// printf("\ncudaMalloc usage %zd\n",
// resources.getMemoryManager().getHighWaterCudaMalloc());
return 0;
}
+18 -9
View File
@@ -6,6 +6,7 @@
*/
#include <faiss/gpu/StandardGpuResources.h>
#include <faiss/gpu/utils/DeviceDefs.cuh>
#include <faiss/gpu/utils/DeviceUtils.h>
#include <faiss/gpu/utils/BlockSelectKernel.cuh>
@@ -29,10 +30,12 @@ DEFINE_int32(iter, 5, "iterations to run");
DEFINE_bool(k_powers, false, "test k powers of 2 from 1 -> max k");
int main(int argc, char** argv) {
using namespace faiss::gpu;
gflags::ParseCommandLineFlags(&argc, &argv, true);
std::vector<float> v = faiss::gpu::randVecs(FLAGS_rows, FLAGS_cols);
faiss::gpu::HostTensor<float, 2, true> hostVal({FLAGS_rows, FLAGS_cols});
std::vector<float> v = randVecs(FLAGS_rows, FLAGS_cols);
HostTensor<float, 2, true> hostVal({FLAGS_rows, FLAGS_cols});
for (int r = 0; r < FLAGS_rows; ++r) {
for (int c = 0; c < FLAGS_cols; ++c) {
@@ -40,8 +43,14 @@ int main(int argc, char** argv) {
}
}
StandardGpuResources res;
res.noTempMemory();
auto resUse = res.getResources();
// Select top-k on GPU
faiss::gpu::DeviceTensor<float, 2, true> gpuVal(hostVal, 0);
DeviceTensor<float, 2, true>
gpuVal(resUse.get(), makeDevAlloc(AllocType::Other, 0), hostVal);
int startK = FLAGS_k;
int limitK = FLAGS_k;
@@ -52,16 +61,16 @@ int main(int argc, char** argv) {
}
for (int k = startK; k <= limitK; k *= 2) {
faiss::gpu::DeviceTensor<float, 2, true> gpuOutVal({FLAGS_rows, k});
faiss::gpu::DeviceTensor<int, 2, true> gpuOutInd({FLAGS_rows, k});
DeviceTensor<float, 2, true>
gpuOutVal(resUse.get(), makeDevAlloc(AllocType::Other, 0), {FLAGS_rows, k});
DeviceTensor<int, 2, true>
gpuOutInd(resUse.get(), makeDevAlloc(AllocType::Other, 0), {FLAGS_rows, k});
for (int i = 0; i < FLAGS_iter; ++i) {
if (FLAGS_warp) {
faiss::gpu::runWarpSelect(gpuVal, gpuOutVal, gpuOutInd,
FLAGS_dir, k, 0);
runWarpSelect(gpuVal, gpuOutVal, gpuOutInd, FLAGS_dir, k, 0);
} else {
faiss::gpu::runBlockSelect(gpuVal, gpuOutVal, gpuOutInd,
FLAGS_dir, k, 0);
runBlockSelect(gpuVal, gpuOutVal, gpuOutInd, FLAGS_dir, k, 0);
}
}
}
+2 -1
View File
@@ -1,9 +1,10 @@
#! /usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python3
# this is a slow computation to test whether ctrl-C handling works
import faiss
import numpy as np
+33 -27
View File
@@ -21,19 +21,21 @@ void testTransposition(bool colMajorVecs,
bool colMajorQueries,
faiss::MetricType metric,
float metricArg = 0) {
int device = faiss::gpu::randVal(0, faiss::gpu::getNumDevices() - 1);
using namespace faiss::gpu;
faiss::gpu::StandardGpuResources res;
int device = randVal(0, getNumDevices() - 1);
StandardGpuResources res;
res.noTempMemory();
int dim = faiss::gpu::randVal(20, 150);
int numVecs = faiss::gpu::randVal(10, 30000);
int numQuery = faiss::gpu::randVal(1, 1024);
int k = std::min(numVecs, faiss::gpu::randVal(20, 70));
int dim = randVal(20, 150);
int numVecs = randVal(10, 30000);
int numQuery = randVal(1, 1024);
int k = std::min(numVecs, randVal(20, 70));
// Input data for CPU
std::vector<float> vecs = faiss::gpu::randVecs(numVecs, dim);
std::vector<float> queries = faiss::gpu::randVecs(numQuery, dim);
std::vector<float> vecs = randVecs(numVecs, dim);
std::vector<float> queries = randVecs(numQuery, dim);
if (metric == faiss::MetricType::METRIC_JensenShannon) {
// make values positive
@@ -64,26 +66,30 @@ void testTransposition(bool colMajorVecs,
cpuDistance.data(), cpuIndices.data());
// The transpose and distance code assumes the desired device is already set
faiss::gpu::DeviceScope scope(device);
DeviceScope scope(device);
auto stream = res.getDefaultStream(device);
// Copy input data to GPU, and pre-transpose both vectors and queries for
// passing
auto gpuVecs = faiss::gpu::toDevice<float, 2>(
nullptr, device, vecs.data(), stream, {numVecs, dim});
auto gpuQueries = faiss::gpu::toDevice<float, 2>(
nullptr, device, queries.data(), stream, {numQuery, dim});
auto gpuVecs = toDeviceNonTemporary<float, 2>(
res.getResources().get(), device, vecs.data(), stream, {numVecs, dim});
auto gpuQueries = toDeviceNonTemporary<float, 2>(
res.getResources().get(), device, queries.data(), stream, {numQuery, dim});
faiss::gpu::DeviceTensor<float, 2, true> vecsT({dim, numVecs});
faiss::gpu::runTransposeAny(gpuVecs, 0, 1, vecsT, stream);
DeviceTensor<float, 2, true>
vecsT(res.getResources().get(),
makeDevAlloc(AllocType::Other, stream), {dim, numVecs});
runTransposeAny(gpuVecs, 0, 1, vecsT, stream);
faiss::gpu::DeviceTensor<float, 2, true> queriesT({dim, numQuery});
faiss::gpu::runTransposeAny(gpuQueries, 0, 1, queriesT, stream);
DeviceTensor<float, 2, true>
queriesT(res.getResources().get(),
makeDevAlloc(AllocType::Other, stream), {dim, numQuery});
runTransposeAny(gpuQueries, 0, 1, queriesT, stream);
std::vector<float> gpuDistance(numQuery * k, 0);
std::vector<faiss::Index::idx_t> gpuIndices(numQuery * k, -1);
faiss::gpu::GpuDistanceParams args;
GpuDistanceParams args;
args.metric = metric;
args.metricArg = metricArg;
args.k = k;
@@ -97,21 +103,21 @@ void testTransposition(bool colMajorVecs,
args.outDistances = gpuDistance.data();
args.outIndices = gpuIndices.data();
faiss::gpu::bfKnn(&res, args);
bfKnn(&res, args);
std::stringstream str;
str << "metric " << metric
<< " colMajorVecs " << colMajorVecs
<< " colMajorQueries " << colMajorQueries;
faiss::gpu::compareLists(cpuDistance.data(),
cpuIndices.data(),
gpuDistance.data(),
gpuIndices.data(),
numQuery, k,
str.str(),
false, false, true,
6e-3f, 0.1f, 0.015f);
compareLists(cpuDistance.data(),
cpuIndices.data(),
gpuDistance.data(),
gpuIndices.data(),
numQuery, k,
str.str(),
false, false, true,
6e-3f, 0.1f, 0.015f);
}
// Test different memory layouts for brute-force k-NN
+10 -10
View File
@@ -131,8 +131,8 @@ TEST(TestGpuIndexIVFPQ, Query_L2) {
cpuIndex.train(opt.numTrain, trainVecs.data());
cpuIndex.add(opt.numAdd, addVecs.data());
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -167,8 +167,8 @@ TEST(TestGpuIndexIVFPQ, Query_IP) {
cpuIndex.train(opt.numTrain, trainVecs.data());
cpuIndex.add(opt.numAdd, addVecs.data());
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -199,8 +199,8 @@ TEST(TestGpuIndexIVFPQ, Float16Coarse) {
cpuIndex.nprobe = opt.nprobe;
cpuIndex.train(opt.numTrain, trainVecs.data());
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -235,8 +235,8 @@ TEST(TestGpuIndexIVFPQ, Add_L2) {
cpuIndex.nprobe = opt.nprobe;
cpuIndex.train(opt.numTrain, trainVecs.data());
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -272,8 +272,8 @@ TEST(TestGpuIndexIVFPQ, Add_IP) {
cpuIndex.nprobe = opt.nprobe;
cpuIndex.train(opt.numTrain, trainVecs.data());
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -300,8 +300,8 @@ TEST(TestGpuIndexIVFPQ, CopyTo) {
std::vector<float> trainVecs = faiss::gpu::randVecs(opt.numTrain, opt.dim);
std::vector<float> addVecs = faiss::gpu::randVecs(opt.numAdd, opt.dim);
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -358,9 +358,8 @@ TEST(TestGpuIndexIVFPQ, CopyFrom) {
cpuIndex.train(opt.numTrain, trainVecs.data());
cpuIndex.add(opt.numAdd, addVecs.data());
// Use garbage values to see if we overwrite them
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -368,6 +367,7 @@ TEST(TestGpuIndexIVFPQ, CopyFrom) {
config.indicesOptions = opt.indicesOpt;
config.useFloat16LookupTables = opt.useFloat16;
// Use garbage values to see if we overwrite them
faiss::gpu::GpuIndexIVFPQ
gpuIndex(&res, 1, 1, 1, 1, faiss::METRIC_L2, config);
gpuIndex.setNumProbes(1);
@@ -401,8 +401,8 @@ TEST(TestGpuIndexIVFPQ, QueryNaN) {
std::vector<float> trainVecs = faiss::gpu::randVecs(opt.numTrain, opt.dim);
std::vector<float> addVecs = faiss::gpu::randVecs(opt.numAdd, opt.dim);
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
@@ -447,8 +447,8 @@ TEST(TestGpuIndexIVFPQ, QueryNaN) {
TEST(TestGpuIndexIVFPQ, AddNaN) {
Options opt;
// Use the default temporary memory management to test the memory manager
faiss::gpu::StandardGpuResources res;
res.noTempMemory();
faiss::gpu::GpuIndexIVFPQConfig config;
config.device = opt.device;
+20 -9
View File
@@ -6,6 +6,7 @@
*/
#include <faiss/gpu/StandardGpuResources.h>
#include <faiss/gpu/test/TestUtils.h>
#include <faiss/gpu/utils/BlockSelectKernel.cuh>
#include <faiss/gpu/utils/DeviceDefs.cuh>
@@ -20,8 +21,12 @@
#include <vector>
void testForSize(int rows, int cols, int k, bool dir, bool warp) {
std::vector<float> v = faiss::gpu::randVecs(rows, cols);
faiss::gpu::HostTensor<float, 2, true> hostVal({rows, cols});
using namespace faiss::gpu;
StandardGpuResources res;
std::vector<float> v = randVecs(rows, cols);
HostTensor<float, 2, true> hostVal({rows, cols});
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < cols; ++c) {
@@ -52,19 +57,25 @@ void testForSize(int rows, int cols, int k, bool dir, bool warp) {
}
// Select top-k on GPU
faiss::gpu::DeviceTensor<float, 2, true> gpuVal(hostVal, 0);
faiss::gpu::DeviceTensor<float, 2, true> gpuOutVal({rows, k});
faiss::gpu::DeviceTensor<int, 2, true> gpuOutInd({rows, k});
DeviceTensor<float, 2, true>
gpuVal(res.getResources().get(),
makeDevAlloc(AllocType::Other, 0), hostVal);
DeviceTensor<float, 2, true>
gpuOutVal(res.getResources().get(),
makeDevAlloc(AllocType::Other, 0), {rows, k});
DeviceTensor<int, 2, true>
gpuOutInd(res.getResources().get(),
makeDevAlloc(AllocType::Other, 0), {rows, k});
if (warp) {
faiss::gpu::runWarpSelect(gpuVal, gpuOutVal, gpuOutInd, dir, k, 0);
runWarpSelect(gpuVal, gpuOutVal, gpuOutInd, dir, k, 0);
} else {
faiss::gpu::runBlockSelect(gpuVal, gpuOutVal, gpuOutInd, dir, k, 0);
runBlockSelect(gpuVal, gpuOutVal, gpuOutInd, dir, k, 0);
}
// Copy back to CPU
faiss::gpu::HostTensor<float, 2, true> outVal(gpuOutVal, 0);
faiss::gpu::HostTensor<int, 2, true> outInd(gpuOutInd, 0);
HostTensor<float, 2, true> outVal(gpuOutVal, 0);
HostTensor<int, 2, true> outInd(gpuOutInd, 0);
for (int r = 0; r < rows; ++r) {
std::unordered_map<int, int> seenIndices;
-4
View File
@@ -5,9 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
// Copyright 2004-present Facebook. All Rights Reserved
#include <cmath>
#include <cstdio>
#include <cstdlib>
@@ -17,7 +14,6 @@
#include <faiss/gpu/StandardGpuResources.h>
#include <faiss/gpu/GpuIndexIVFPQ.h>
#include <faiss/gpu/GpuCloner.h>
#include <faiss/gpu/GpuAutoTune.h>
#include <faiss/index_io.h>
+37 -1
View File
@@ -73,13 +73,22 @@ class EvalIVFPQAccuracy(unittest.TestCase):
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)
ts.append(time.time())
# Validate the layout of the memory info
mem_info = res.getMemoryInfo()
assert type(mem_info) == dict
assert type(mem_info[0]['FlatData']) == tuple
assert type(mem_info[0]['FlatData'][0]) == int
assert type(mem_info[0]['FlatData'][1]) == int
gpu_index.setNumProbes(4)
D, Inew = gpu_index.search(xq, 10)
ts.append(time.time())
print('times:', [t - ts[0] for t in ts])
self.assertGreaterEqual((Iref == Inew).sum(), Iref.size)
# Give us some margin of error
self.assertGreaterEqual((Iref == Inew).sum(), Iref.size - 50)
if faiss.get_num_gpus() == 1:
return
@@ -310,5 +319,32 @@ class TestAlternativeDistances(unittest.TestCase):
self.do_test(faiss.METRIC_Lp, 0.7)
class TestGpuRef(unittest.TestCase):
def test_gpu_ref(self):
# this crashes
dim = 256
training_data = np.random.randint(256, size=(10000, dim // 8)).astype('uint8')
centroids = 330
def create_cpu(dim):
quantizer = faiss.IndexBinaryFlat(dim)
return faiss.IndexBinaryIVF(quantizer, dim, centroids)
def create_gpu(dim):
gpu_quantizer = faiss.index_cpu_to_all_gpus(faiss.IndexFlatL2(dim))
index = create_cpu(dim)
index.clustering_index = gpu_quantizer
index.dont_dealloc_me = gpu_quantizer
return index
index = create_gpu(dim)
index.verbose = True
index.cp.verbose = True
index.train(training_data)
if __name__ == '__main__':
unittest.main()
+2 -2
View File
@@ -1,10 +1,10 @@
#! /usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#! /usr/bin/env python3
from __future__ import print_function
import unittest
import numpy as np
+13 -4
View File
@@ -91,10 +91,19 @@ def search_raw_array_pytorch(res, xb, xq, k, D=None, I=None,
D_ptr = swig_ptr_from_FloatTensor(D)
I_ptr = swig_ptr_from_LongTensor(I)
faiss.bruteForceKnn(res, metric,
xb_ptr, xb_row_major, nb,
xq_ptr, xq_row_major, nq,
d, k, D_ptr, I_ptr)
args = faiss.GpuDistanceParams()
args.metric = metric
args.k = k
args.dims = d
args.vectors = xb_ptr
args.vectorsRowMajor = xb_row_major
args.numVectors = nb
args.queries = xq_ptr
args.queriesRowMajor = xq_row_major
args.numQueries = nq
args.outDistances = D_ptr
args.outIndices = I_ptr
faiss.bfKnn(res, args)
return D, I
+23 -13
View File
@@ -103,22 +103,32 @@ void convertTensor(cudaStream_t stream,
}
template <typename From, typename To, int Dim>
DeviceTensor<To, Dim, true> convertTensor(GpuResources* res,
cudaStream_t stream,
Tensor<From, Dim, true>& in) {
DeviceTensor<To, Dim, true> out;
if (res) {
out = std::move(DeviceTensor<To, Dim, true>(
res->getMemoryManagerCurrentDevice(),
in.sizes(),
stream));
} else {
out = std::move(DeviceTensor<To, Dim, true>(in.sizes()));
}
DeviceTensor<To, Dim, true>
convertTensorTemporary(GpuResources* res,
cudaStream_t stream,
Tensor<From, Dim, true>& in) {
FAISS_ASSERT(res);
DeviceTensor<To, Dim, true> out(
res, makeTempAlloc(AllocType::Other, stream),
in.sizes());
convertTensor(stream, in, out);
return out;
}
template <typename From, typename To, int Dim>
DeviceTensor<To, Dim, true>
convertTensorNonTemporary(GpuResources* res,
cudaStream_t stream,
Tensor<From, Dim, true>& in) {
FAISS_ASSERT(res);
DeviceTensor<To, Dim, true> out(
res, makeDevAlloc(AllocType::Other, stream),
in.sizes());
convertTensor(stream, in, out);
return out;
}
} } // namespace

Some files were not shown because too many files have changed in this diff Show More