Skip to content

Commit 7657e81

Browse files
Change index_cpu_to_gpu to throw for indices not implemented on GPU (facebookresearch#3336)
Summary: Pull Request resolved: facebookresearch#3336 Issues: facebookresearch#3269 facebookresearch#3024 List of implemented GPU indices: https://github.com/facebookresearch/faiss/wiki/Faiss-on-the-GPU#implemented-indexes Reviewed By: mdouze Differential Revision: D55577576 fbshipit-source-id: 49f490cfba6784661e378acf4de3cce4195bb43b
1 parent f34588a commit 7657e81

8 files changed

+130
-35
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ We try to indicate most contributions here with the contributor names who are no
99
the Facebook Faiss team. Feel free to add entries here if you submit a PR.
1010

1111
## [Unreleased]
12+
### Changed
13+
- Previously, when moving indices to GPU with coarse quantizers that were not implemented on GPU, the cloner would silently fallback to CPU. This version will now throw an exception instead and the calling code would need to explicitly allow fallback to CPU by setting a flag in cloner config.
1214

1315
## [1.8.0] - 2024-02-27
1416
### Added

faiss/gpu/GpuCloner.cpp

+9-4
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ Index* ToGpuCloner::clone_Index(const Index* index) {
153153
config.indicesOptions = indicesOptions;
154154
config.flatConfig.useFloat16 = useFloat16CoarseQuantizer;
155155
config.use_raft = use_raft;
156+
config.allowCpuCoarseQuantizer = allowCpuCoarseQuantizer;
156157

157158
GpuIndexIVFFlat* res = new GpuIndexIVFFlat(
158159
provider, ifl->d, ifl->nlist, ifl->metric_type, config);
@@ -205,6 +206,7 @@ Index* ToGpuCloner::clone_Index(const Index* index) {
205206
config.usePrecomputedTables = usePrecomputed;
206207
config.use_raft = use_raft;
207208
config.interleavedLayout = use_raft;
209+
config.allowCpuCoarseQuantizer = allowCpuCoarseQuantizer;
208210

209211
GpuIndexIVFPQ* res = new GpuIndexIVFPQ(provider, ipq, config);
210212

@@ -214,8 +216,13 @@ Index* ToGpuCloner::clone_Index(const Index* index) {
214216

215217
return res;
216218
} else {
217-
// default: use CPU cloner
218-
return Cloner::clone_Index(index);
219+
// use CPU cloner for IDMap and PreTransform
220+
auto index_idmap = dynamic_cast<const IndexIDMap*>(index);
221+
auto index_pt = dynamic_cast<const IndexPreTransform*>(index);
222+
if (index_idmap || index_pt) {
223+
return Cloner::clone_Index(index);
224+
}
225+
FAISS_THROW_MSG("This index type is not implemented on GPU.");
219226
}
220227
}
221228

@@ -224,8 +231,6 @@ faiss::Index* index_cpu_to_gpu(
224231
int device,
225232
const faiss::Index* index,
226233
const GpuClonerOptions* options) {
227-
auto index_pq = dynamic_cast<const faiss::IndexPQ*>(index);
228-
FAISS_THROW_IF_MSG(index_pq, "This index type is not implemented on GPU.");
229234
GpuClonerOptions defaults;
230235
ToGpuCloner cl(provider, device, options ? *options : defaults);
231236
return cl.clone_Index(index);

faiss/gpu/GpuClonerOptions.h

+6
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ struct GpuClonerOptions {
4343
#else
4444
bool use_raft = false;
4545
#endif
46+
47+
/// This flag controls the CPU fallback logic for coarse quantizer
48+
/// component of the index. When set to false (default), the cloner will
49+
/// throw an exception for indices not implemented on GPU. When set to
50+
/// true, it will fallback to a CPU implementation.
51+
bool allowCpuCoarseQuantizer = false;
4652
};
4753

4854
struct GpuMultipleClonerOptions : public GpuClonerOptions {

faiss/gpu/GpuIndexIVF.cu

+24-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <faiss/IndexFlat.h>
99
#include <faiss/IndexIVF.h>
10+
#include <faiss/clone_index.h>
1011
#include <faiss/gpu/GpuCloner.h>
1112
#include <faiss/gpu/GpuIndexFlat.h>
1213
#include <faiss/gpu/GpuIndexIVF.h>
@@ -172,10 +173,29 @@ void GpuIndexIVF::copyFrom(const faiss::IndexIVF* index) {
172173
// over to the GPU, on the same device that we are on.
173174
GpuResourcesProviderFromInstance pfi(getResources());
174175

175-
GpuClonerOptions options;
176-
auto cloner = ToGpuCloner(&pfi, getDevice(), options);
177-
178-
quantizer = cloner.clone_Index(index->quantizer);
176+
// Attempt to clone the index to GPU. If it fails because the coarse
177+
// quantizer is not implemented on GPU and the flag to allow CPU
178+
// fallback is set, retry it with CPU cloner and re-throw errors.
179+
try {
180+
GpuClonerOptions options;
181+
auto cloner = ToGpuCloner(&pfi, getDevice(), options);
182+
quantizer = cloner.clone_Index(index->quantizer);
183+
} catch (const std::exception& e) {
184+
if (strstr(e.what(), "not implemented on GPU")) {
185+
if (ivfConfig_.allowCpuCoarseQuantizer) {
186+
Cloner cpuCloner;
187+
quantizer = cpuCloner.clone_Index(index->quantizer);
188+
} else {
189+
FAISS_THROW_MSG(
190+
"This index type is not implemented on "
191+
"GPU and allowCpuCoarseQuantizer is set to false. "
192+
"Please set the flag to true to allow the CPU "
193+
"fallback in cloning.");
194+
}
195+
} else {
196+
throw;
197+
}
198+
}
179199
own_fields = true;
180200
} else {
181201
// Otherwise, this is a GPU coarse quantizer index instance found in a

faiss/gpu/GpuIndexIVF.h

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ struct GpuIndexIVFConfig : public GpuIndexConfig {
2626

2727
/// Configuration for the coarse quantizer object
2828
GpuIndexFlatConfig flatConfig;
29+
30+
/// This flag controls the CPU fallback logic for coarse quantizer
31+
/// component of the index. When set to false (default), the cloner will
32+
/// throw an exception for indices not implemented on GPU. When set to
33+
/// true, it will fallback to a CPU implementation.
34+
bool allowCpuCoarseQuantizer = false;
2935
};
3036

3137
/// Base class of all GPU IVF index types. This (for now) deliberately does not

faiss/gpu/test/test_gpu_index.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,10 @@ class TestGpuAutoTune(unittest.TestCase):
589589

590590
def test_params(self):
591591
index = faiss.index_factory(32, "IVF65536_HNSW,PQ16")
592-
index = faiss.index_cpu_to_gpu(faiss.StandardGpuResources(), 0, index)
592+
res = faiss.StandardGpuResources()
593+
options = faiss.GpuClonerOptions()
594+
options.allowCpuCoarseQuantizer = True
595+
index = faiss.index_cpu_to_gpu(res, 0, index, options)
593596
ps = faiss.GpuParameterSpace()
594597
ps.initialize(index)
595598
for i in range(ps.parameter_ranges.size()):

faiss/gpu/test/test_index_cpu_to_gpu.py

+79-19
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,86 @@
44

55

66
class TestMoveToGpu(unittest.TestCase):
7-
def test_index_cpu_to_gpu(self):
7+
8+
@classmethod
9+
def setUpClass(cls):
10+
cls.res = faiss.StandardGpuResources()
11+
12+
def create_index(self, factory_string):
813
dimension = 128
914
n = 2500
1015
db_vectors = np.random.random((n, dimension)).astype('float32')
11-
code_size = 16
12-
res = faiss.StandardGpuResources()
13-
index_pq = faiss.IndexPQ(dimension, code_size, 6)
14-
index_pq.train(db_vectors)
15-
index_pq.add(db_vectors)
16-
self.assertRaisesRegex(Exception, ".*not implemented.*",
17-
faiss.index_cpu_to_gpu, res, 0, index_pq)
18-
19-
def test_index_cpu_to_gpu_does_not_throw_with_index_flat(self):
20-
dimension = 128
21-
n = 100
22-
db_vectors = np.random.random((n, dimension)).astype('float32')
23-
res = faiss.StandardGpuResources()
24-
index_flat = faiss.IndexFlatL2(dimension)
25-
index_flat.add(db_vectors)
16+
index = faiss.index_factory(dimension, factory_string)
17+
index.train(db_vectors)
18+
if factory_string.startswith("IDMap"):
19+
index.add_with_ids(db_vectors, np.arange(n))
20+
else:
21+
index.add(db_vectors)
22+
return index
23+
24+
def create_and_clone(self, factory_string,
25+
allowCpuCoarseQuantizer=None,
26+
use_raft=None):
27+
idx = self.create_index(factory_string)
28+
config = faiss.GpuClonerOptions()
29+
if allowCpuCoarseQuantizer is not None:
30+
config.allowCpuCoarseQuantizer = allowCpuCoarseQuantizer
31+
if use_raft is not None:
32+
config.use_raft = use_raft
33+
faiss.index_cpu_to_gpu(self.res, 0, idx, config)
34+
35+
def verify_throws_not_implemented_exception(self, factory_string):
36+
try:
37+
self.create_and_clone(factory_string)
38+
except Exception as e:
39+
if "not implemented" not in str(e):
40+
self.fail("Expected an exception but no exception was "
41+
"thrown for factory_string: %s." % factory_string)
42+
43+
def verify_clones_successfully(self, factory_string,
44+
allowCpuCoarseQuantizer=None,
45+
use_raft=None):
46+
try:
47+
self.create_and_clone(
48+
factory_string,
49+
allowCpuCoarseQuantizer=allowCpuCoarseQuantizer,
50+
use_raft=use_raft)
51+
except Exception as e:
52+
self.fail("Unexpected exception thrown factory_string: "
53+
"%s; error message: %s." % (factory_string, str(e)))
54+
55+
def test_not_implemented_indices(self):
56+
self.verify_throws_not_implemented_exception("PQ16")
57+
self.verify_throws_not_implemented_exception("LSHrt")
58+
self.verify_throws_not_implemented_exception("HNSW")
59+
self.verify_throws_not_implemented_exception("HNSW,PQ16")
60+
self.verify_throws_not_implemented_exception("IDMap,PQ16")
61+
self.verify_throws_not_implemented_exception("IVF256,ITQ64,SH1.2")
62+
63+
def test_implemented_indices(self):
64+
self.verify_clones_successfully("Flat")
65+
self.verify_clones_successfully("IVF1,Flat")
66+
self.verify_clones_successfully("IVF32,PQ8")
67+
self.verify_clones_successfully("IDMap,Flat")
68+
self.verify_clones_successfully("PCA12,IVF32,Flat")
69+
self.verify_clones_successfully("PCA32,IVF32,PQ8")
70+
self.verify_clones_successfully("PCA32,IVF32,PQ8np")
71+
72+
# set use_raft to false, these index types are not supported on RAFT
73+
self.verify_clones_successfully("IVF32,SQ8", use_raft=False)
74+
self.verify_clones_successfully(
75+
"PCA32,IVF32,SQ8", use_raft=False)
76+
77+
def test_with_flag(self):
78+
self.verify_clones_successfully("IVF32_HNSW,Flat",
79+
allowCpuCoarseQuantizer=True)
80+
self.verify_clones_successfully("IVF256(PQ2x4fs),Flat",
81+
allowCpuCoarseQuantizer=True)
82+
83+
def test_with_flag_set_to_false(self):
2684
try:
27-
faiss.index_cpu_to_gpu(res, 0, index_flat)
28-
except Exception:
29-
self.fail("index_cpu_to_gpu() threw an unexpected exception.")
85+
self.verify_clones_successfully("IVF32_HNSW,Flat",
86+
allowCpuCoarseQuantizer=False)
87+
except Exception as e:
88+
if "set the flag to true to allow the CPU fallback" not in str(e):
89+
self.fail("Unexepected error message thrown: %s." % str(e))

faiss/impl/FaissAssert.h

-7
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,6 @@
9494
} \
9595
} while (false)
9696

97-
#define FAISS_THROW_IF_MSG(X, MSG) \
98-
do { \
99-
if (X) { \
100-
FAISS_THROW_FMT("Error: '%s' failed: " MSG, #X); \
101-
} \
102-
} while (false)
103-
10497
#define FAISS_THROW_IF_NOT_MSG(X, MSG) \
10598
do { \
10699
if (!(X)) { \

0 commit comments

Comments
 (0)