Portable SWIG Vectors (#1742)

Summary:
After initial positive feedback to the idea in https://github.com/facebookresearch/faiss/issues/1741 from mdouze, here are the patches
I currently have as a basis for discussion.

Matthijs suggests to not bother with the deprecation warnings at all, which is fine for me
as well, though I would normally still advocate to provide users with _some_ advance notice
before removing parts of an interface.

Fixes https://github.com/facebookresearch/faiss/issues/1741

PS. The deprecation warning is only shown once per session (per class)
PPS. I have tested in https://github.com/conda-forge/faiss-split-feedstock/pull/32 that the respective
classes remain available both through `import faiss` and `from faiss import *`.

Pull Request resolved: https://github.com/facebookresearch/faiss/pull/1742

Reviewed By: mdouze

Differential Revision: D26978886

Pulled By: beauby

fbshipit-source-id: b52e2b5b5b0117af7cd95ef5df3128e9914633ad
This commit is contained in:
H. Vetinari
2021-04-02 07:11:47 -07:00
committed by Facebook GitHub Bot
parent 06f1ef86ac
commit 9c58ae00f1
5 changed files with 106 additions and 50 deletions
+2
View File
@@ -15,6 +15,8 @@ at the moment.
### Changed
- The order of xb an xq was different between `faiss.knn` and `faiss.knn_gpu`.
Also the metric argument was called distance_type.
- The typed vectors (LongVector, LongLongVector, etc.) of the SWIG interface have
been deprecated. They have been replaced with Int32Vector, Int64Vector, etc.
### Fixed
- Fixed a bug causing kNN search functions for IndexBinaryHash and
+78 -16
View File
@@ -5,13 +5,14 @@
#@nolint
# not linting this file because it imports * form swigfaiss, which
# not linting this file because it imports * from swigfaiss, which
# causes a ton of useless warnings.
import numpy as np
import sys
import inspect
import array
import warnings
# We import * so that the symbol foo can be accessed as faiss.foo.
from .loader import *
@@ -707,6 +708,47 @@ for symbol in dir(this_module):
if issubclass(the_class, IndexNSG):
handle_NSG(the_class)
###########################################
# Utility to add a deprecation warning to
# classes from the SWIG interface
###########################################
def _make_deprecated_swig_class(deprecated_name, base_name):
"""
Dynamically construct deprecated classes as wrappers around renamed ones
The deprecation warning added in their __new__-method will trigger upon
construction of an instance of the class, but only once per session.
We do this here (in __init__.py) because the base classes are defined in
the SWIG interface, making it cumbersome to add the deprecation there.
Parameters
----------
deprecated_name : string
Name of the class to be deprecated; _not_ present in SWIG interface.
base_name : string
Name of the class that is replacing deprecated_name; must already be
imported into the current namespace.
Returns
-------
None
However, the deprecated class gets added to the faiss namespace
"""
base_class = globals()[base_name]
def new_meth(cls, *args, **kwargs):
msg = f"The class faiss.{deprecated_name} is deprecated in favour of faiss.{base_name}!"
warnings.warn(msg, DeprecationWarning, stacklevel=2)
instance = super(base_class, cls).__new__(cls, *args, **kwargs)
return instance
# three-argument version of "type" uses (name, tuple-of-bases, dict-of-attributes)
klazz = type(deprecated_name, (base_class,), {"__new__": new_meth})
# this ends up adding the class to the "faiss" namespace, in a way that it
# is available both through "import faiss" and "from faiss import *"
globals()[deprecated_name] = klazz
###########################################
# Add Python references to objects
@@ -799,7 +841,7 @@ def index_cpu_to_gpu_multiple_py(resources, index, co=None, gpus=None):
if gpus is None:
gpus = range(len(resources))
vres = GpuResourcesVector()
vdev = IntVector()
vdev = Int32Vector()
for i, res in zip(gpus, resources):
vdev.push_back(i)
vres.push_back(res)
@@ -940,23 +982,43 @@ def knn_gpu(res, xq, xb, k, D=None, I=None, metric=METRIC_L2):
# numpy array / std::vector conversions
###########################################
# mapping from vector names in swigfaiss.swig and the numpy dtype names
vector_name_map = {
'Float': 'float32',
'Byte': 'uint8',
'Char': 'int8',
'Uint64': 'uint64',
'LongLong': 'int64',
'Int': 'int32',
'Double': 'float64'
sizeof_long = array.array('l').itemsize
deprecated_name_map = {
# deprecated: replacement
'Float': 'Float32',
'Double': 'Float64',
'Char': 'Int8',
'Int': 'Int32',
'Long': 'Int32' if sizeof_long == 4 else 'Int64',
'LongLong': 'Int64',
'Byte': 'UInt8',
# previously misspelled variant
'Uint64': 'UInt64',
}
sizeof_long = array.array('l').itemsize
if sizeof_long == 4:
vector_name_map["Long"] = 'int32'
elif sizeof_long == 8:
vector_name_map["Long"] = 'int64'
for depr_prefix, base_prefix in deprecated_name_map.items():
_make_deprecated_swig_class(depr_prefix + "Vector", base_prefix + "Vector")
# same for the three legacy *VectorVector classes
if depr_prefix in ['Float', 'Long', 'Byte']:
_make_deprecated_swig_class(depr_prefix + "VectorVector",
base_prefix + "VectorVector")
# mapping from vector names in swigfaiss.swig and the numpy dtype names
# TODO: once deprecated classes are removed, remove the dict and just use .lower() below
vector_name_map = {
'Float32': 'float32',
'Float64': 'float64',
'Int8': 'int8',
'Int16': 'int16',
'Int32': 'int32',
'Int64': 'int64',
'UInt8': 'uint8',
'UInt16': 'uint16',
'UInt32': 'uint32',
'UInt64': 'uint64',
**{k: v.lower() for k, v in deprecated_name_map.items()}
}
def vector_to_array(v):
+24 -26
View File
@@ -207,23 +207,21 @@ namespace std {
%include <std_shared_ptr.i>
// primitive array types
%template(FloatVector) std::vector<float>;
%template(DoubleVector) std::vector<double>;
%template(ByteVector) std::vector<uint8_t>;
%template(CharVector) std::vector<char>;
// NOTE(hoss): Using unsigned long instead of uint64_t because OSX defines
// uint64_t as unsigned long long, which SWIG is not aware of.
%template(Uint64Vector) std::vector<unsigned long>;
%template(LongVector) std::vector<long>;
%template(LongLongVector) std::vector<long long>;
%template(IntVector) std::vector<int>;
%template(UInt32Vector) std::vector<uint32_t>;
%template(Float32Vector) std::vector<float>;
%template(Float64Vector) std::vector<double>;
%template(Int8Vector) std::vector<int8_t>;
%template(Int16Vector) std::vector<int16_t>;
%template(Int32Vector) std::vector<int32_t>;
%template(Int64Vector) std::vector<int64_t>;
%template(UInt8Vector) std::vector<uint8_t>;
%template(UInt16Vector) std::vector<uint16_t>;
%template(UInt32Vector) std::vector<uint32_t>;
%template(UInt64Vector) std::vector<uint64_t>;
%template(FloatVectorVector) std::vector<std::vector<float> >;
%template(ByteVectorVector) std::vector<std::vector<unsigned char> >;
%template(LongVectorVector) std::vector<std::vector<long> >;
%template(Float32VectorVector) std::vector<std::vector<float> >;
%template(UInt8VectorVector) std::vector<std::vector<uint8_t> >;
%template(Int32VectorVector) std::vector<std::vector<int32_t> >;
%template(Int64VectorVector) std::vector<std::vector<int64_t> >;
%template(VectorTransformVector) std::vector<faiss::VectorTransform*>;
%template(OperatingPointVector) std::vector<faiss::OperatingPoint>;
%template(InvertedListsPtrVector) std::vector<faiss::InvertedLists*>;
@@ -307,9 +305,9 @@ void gpu_sync_all_devices()
%}
%template() std::pair<int, unsigned long>;
%template() std::map<std::string, std::pair<int, unsigned long> >;
%template() std::map<int, std::map<std::string, std::pair<int, unsigned long> > >;
%template() std::pair<int, uint64_t>;
%template() std::map<std::string, std::pair<int, uint64_t> >;
%template() std::map<int, std::map<std::string, std::pair<int, uint64_t> > >;
// causes weird wrapper bug
%ignore *::allocMemoryHandle;
@@ -324,12 +322,12 @@ typedef CUstream_st* cudaStream_t;
%inline %{
// interop between pytorch exposed cudaStream_t and faiss
cudaStream_t cast_integer_to_cudastream_t(long long x) {
cudaStream_t cast_integer_to_cudastream_t(int64_t x) {
return (cudaStream_t) x;
}
long long cast_cudastream_t_to_integer(cudaStream_t x) {
return (long) x;
int64_t cast_cudastream_t_to_integer(cudaStream_t x) {
return (int64_t) x;
}
%}
@@ -914,23 +912,23 @@ void *memcpy(void *dest, const void *src, size_t n);
*******************************************************************/
%inline %{
uint8_t * cast_integer_to_uint8_ptr (long long x) {
uint8_t * cast_integer_to_uint8_ptr (int64_t x) {
return (uint8_t*)x;
}
float * cast_integer_to_float_ptr (long long x) {
float * cast_integer_to_float_ptr (int64_t x) {
return (float*)x;
}
faiss::Index::idx_t* cast_integer_to_idx_t_ptr (long long x) {
faiss::Index::idx_t* cast_integer_to_idx_t_ptr (int64_t x) {
return (faiss::Index::idx_t*)x;
}
int * cast_integer_to_int_ptr (long long x) {
int * cast_integer_to_int_ptr (int64_t x) {
return (int*)x;
}
void * cast_integer_to_void_ptr (long long x) {
void * cast_integer_to_void_ptr (int64_t x) {
return (void*)x;
}
@@ -957,7 +955,7 @@ struct MapLong2Long {
}
}
long search(int64_t key) {
int64_t search(int64_t key) {
if (map.count(key) == 0) {
return -1;
} else {
+1 -7
View File
@@ -614,13 +614,7 @@ class TestSWIGWrap(unittest.TestCase):
def test_int64(self):
# see https://github.com/facebookresearch/faiss/issues/1529
sizeof_long = array.array("l").itemsize
if sizeof_long == 4:
v = faiss.LongLongVector()
elif sizeof_long == 8:
v = faiss.LongVector()
else:
raise AssertionError("weird long size")
v = faiss.Int64Vector()
for i in range(10):
v.push_back(i)
+1 -1
View File
@@ -19,7 +19,7 @@ class TestOOMException(unittest.TestCase):
# make sure that allocing more than 10G will fail
resource.setrlimit(resource.RLIMIT_AS, (10 * 1024 * 1024, hard_as))
try:
x = faiss.IntVector()
x = faiss.Int32Vector()
try:
x.resize(10**11) # 400 G of RAM
except MemoryError: