usearch 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,12 @@
9
9
  * @see NodeJS docs: https://nodejs.org/api/addons.html#hello-world
10
10
  *
11
11
  */
12
+
13
+ #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
14
+ #define _USE_MATH_DEFINES
15
+ #define NAPI_CPP_EXCEPTIONS
16
+ #endif
17
+
12
18
  #include <napi.h>
13
19
  #include <node_api.h>
14
20
 
@@ -87,8 +93,8 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
87
93
  accuracy = accuracy_t::f64_k;
88
94
  else if (accuracy_str == "f16")
89
95
  accuracy = accuracy_t::f16_k;
90
- else if (accuracy_str == "i8q100")
91
- accuracy = accuracy_t::i8q100_k;
96
+ else if (accuracy_str == "f8")
97
+ accuracy = accuracy_t::f8_k;
92
98
  else {
93
99
  Napi::TypeError::New(env, "Supported metrics are: [ip, cos, l2_sq, haversine]")
94
100
  .ThrowAsJavaScriptException();
@@ -0,0 +1,150 @@
1
+ #import "USearchObjective.h"
2
+
3
+ #pragma clang diagnostic push
4
+ #pragma clang diagnostic ignored "-Wdocumentation"
5
+ #import "advanced.hpp"
6
+ #pragma clang diagnostic pop
7
+
8
+ using namespace unum::usearch;
9
+ using namespace unum;
10
+
11
+ using distance_t = punned_distance_t;
12
+ using native_index_t = auto_index_gt<UInt32>;
13
+ using shared_index_t = std::shared_ptr<native_index_t>;
14
+
15
+ @interface Index ()
16
+
17
+ @property (readonly) shared_index_t native;
18
+
19
+ - (instancetype)initWithIndex:(shared_index_t)native;
20
+
21
+ @end
22
+
23
+ @implementation Index
24
+
25
+ - (instancetype)initWithIndex:(shared_index_t)native {
26
+ self = [super init];
27
+ _native = native;
28
+ return self;
29
+ }
30
+
31
+ - (Boolean)isEmpty {
32
+ return _native->size() != 0;
33
+ }
34
+
35
+ - (UInt32)dimensions {
36
+ return static_cast<UInt32>(_native->dimensions());
37
+ }
38
+
39
+ - (UInt32)connectivity {
40
+ return static_cast<UInt32>(_native->connectivity());
41
+ }
42
+
43
+ - (UInt32)length {
44
+ return static_cast<UInt32>(_native->size());
45
+ }
46
+
47
+ - (UInt32)capacity {
48
+ return static_cast<UInt32>(_native->capacity());
49
+ }
50
+
51
+ - (UInt32)expansion_add {
52
+ return static_cast<UInt32>(_native->config().expansion_add);
53
+ }
54
+
55
+ - (UInt32)expansion_search {
56
+ return static_cast<UInt32>(_native->config().expansion_search);
57
+ }
58
+
59
+ + (instancetype)indexIP:(UInt32)dimensions connectivity:(UInt32)connectivity {
60
+ std::size_t dims = static_cast<std::size_t>(dimensions);
61
+ config_t config;
62
+ config.connectivity = static_cast<std::size_t>(connectivity);
63
+ shared_index_t ptr = std::make_shared<native_index_t>(native_index_t::ip(dims, accuracy_t::f32_k, config));
64
+ return [[Index alloc] initWithIndex:ptr];
65
+ }
66
+
67
+ + (instancetype)indexL2:(UInt32)dimensions connectivity:(UInt32)connectivity {
68
+ std::size_t dims = static_cast<std::size_t>(dimensions);
69
+ config_t config;
70
+ config.connectivity = static_cast<std::size_t>(connectivity);
71
+ shared_index_t ptr = std::make_shared<native_index_t>(native_index_t::l2(dims, accuracy_t::f32_k, config));
72
+ return [[Index alloc] initWithIndex:ptr];
73
+ }
74
+
75
+ + (instancetype)indexHaversine:(UInt32)connectivity {
76
+ config_t config;
77
+ config.connectivity = static_cast<std::size_t>(connectivity);
78
+ shared_index_t ptr = std::make_shared<native_index_t>(native_index_t::haversine(accuracy_t::f32_k, config));
79
+ return [[Index alloc] initWithIndex:ptr];
80
+ }
81
+
82
+ - (void)addSingle:(UInt32)label
83
+ vector:(Float32 const * _Nonnull)vector {
84
+ _native->add(label, vector);
85
+ }
86
+
87
+ - (UInt32)searchSingle:(Float32 const* _Nonnull)vector
88
+ count:(UInt32)wanted
89
+ labels:(UInt32* _Nullable)labels
90
+ distances:(Float32* _Nullable)distances {
91
+ std::size_t found = _native->search(vector, static_cast<std::size_t>(wanted), labels, distances);
92
+ return static_cast<UInt32>(found);
93
+ }
94
+
95
+ - (void)addPrecise:(UInt32)label
96
+ vector:(Float64 const * _Nonnull)vector {
97
+ _native->add(label, (f64_t const *)vector);
98
+ }
99
+
100
+ - (UInt32)searchPrecise:(Float64 const* _Nonnull)vector
101
+ count:(UInt32)wanted
102
+ labels:(UInt32* _Nullable)labels
103
+ distances:(Float32* _Nullable)distances {
104
+ std::size_t found = _native->search((f64_t const *)vector, static_cast<std::size_t>(wanted), labels, distances);
105
+ return static_cast<UInt32>(found);
106
+ }
107
+
108
+ - (void)addImprecise:(UInt32)label
109
+ vector:(void const * _Nonnull)vector {
110
+ _native->add(label, (f16_converted_t const *)vector);
111
+ }
112
+
113
+ - (UInt32)searchImprecise:(void const* _Nonnull)vector
114
+ count:(UInt32)wanted
115
+ labels:(UInt32* _Nullable)labels
116
+ distances:(Float32* _Nullable)distances {
117
+ std::size_t found = _native->search((f16_converted_t const *)vector, static_cast<std::size_t>(wanted), labels, distances);
118
+ return static_cast<UInt32>(found);
119
+ }
120
+
121
+ - (void)clear { _native->clear(); }
122
+
123
+ - (void)save:(NSString*)path {
124
+ char const *path_c = [path UTF8String];
125
+ if (!path_c)
126
+ @throw [NSException exceptionWithName:@"Can't save to disk"
127
+ reason:@"The path must be convertible to UTF8"
128
+ userInfo:nil];
129
+ _native->save(path_c);
130
+ }
131
+
132
+ - (void)load:(NSString*)path {
133
+ char const *path_c = [path UTF8String];
134
+ if (!path_c)
135
+ @throw [NSException exceptionWithName:@"Can't load from disk"
136
+ reason:@"The path must be convertible to UTF8"
137
+ userInfo:nil];
138
+ _native->load(path_c);
139
+ }
140
+
141
+ - (void)view:(NSString*)path {
142
+ char const *path_c = [path UTF8String];
143
+ if (!path_c)
144
+ @throw [NSException exceptionWithName:@"Can't view from disk"
145
+ reason:@"The path must be convertible to UTF8"
146
+ userInfo:nil];
147
+ _native->view(path_c);
148
+ }
149
+
150
+ @end
@@ -0,0 +1,69 @@
1
+ #pragma once
2
+
3
+ #import <Foundation/Foundation.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ typedef NS_ENUM(NSUInteger, Quantization) {
8
+ kSinglePrecision,
9
+ kHalfPrecision,
10
+ kDoublePrecision
11
+ };
12
+
13
+ @interface Index: NSObject
14
+
15
+ @property (readonly) UInt32 dimensions;
16
+ @property (readonly) UInt32 connectivity;
17
+ @property (readonly) UInt32 expansion_add;
18
+ @property (readonly) UInt32 expansion_search;
19
+
20
+ @property (readonly) UInt32 length;
21
+ @property (readonly) UInt32 capacity;
22
+ @property (readonly) Boolean isEmpty;
23
+
24
+ - (instancetype)init
25
+ NS_UNAVAILABLE;
26
+
27
+ + (instancetype)indexIP:(UInt32)dimensions connectivity:(UInt32)connectivity NS_SWIFT_NAME(indexIP(dimensions:connectivity:));
28
+ + (instancetype)indexL2:(UInt32)dimensions connectivity:(UInt32)connectivity NS_SWIFT_NAME(l2(dimensions:connectivity:));
29
+ + (instancetype)indexHaversine:(UInt32)connectivity NS_SWIFT_NAME(indexHaversine(connectivity:));
30
+
31
+ - (void)addSingle:(UInt32)label
32
+ vector:(Float32 const* _Nonnull)vector
33
+ NS_SWIFT_NAME(addSingle(label:vector:));
34
+
35
+ - (UInt32)searchSingle:(Float32 const* _Nonnull)vector
36
+ count:(UInt32)count
37
+ labels:(UInt32* _Nullable)labels
38
+ distances:(Float32* _Nullable)distances
39
+ NS_SWIFT_NAME(searchSingle(vector:count:labels:distances:));
40
+
41
+ - (void)addPrecise:(UInt32)label
42
+ vector:(Float64 const* _Nonnull)vector
43
+ NS_SWIFT_NAME(addPrecise(label:vector:));
44
+
45
+ - (UInt32)searchPrecise:(Float64 const* _Nonnull)vector
46
+ count:(UInt32)count
47
+ labels:(UInt32* _Nullable)labels
48
+ distances:(Float32* _Nullable)distances
49
+ NS_SWIFT_NAME(searchPrecise(vector:count:labels:distances:));
50
+
51
+ - (void)addImprecise:(UInt32)label
52
+ vector:(void const* _Nonnull)vector
53
+ NS_SWIFT_NAME(addImprecise(label:vector:));
54
+
55
+ - (UInt32)searchImprecise:(void const* _Nonnull)vector
56
+ count:(UInt32)count
57
+ labels:(UInt32* _Nullable)labels
58
+ distances:(Float32* _Nullable)distances
59
+ NS_SWIFT_NAME(searchImprecise(vector:count:labels:distances:));
60
+
61
+
62
+ - (void)save:(NSString*)path NS_SWIFT_NAME(dump(path:));
63
+ - (void)load:(NSString*)path NS_SWIFT_NAME(load(path:));
64
+ - (void)view:(NSString*)path NS_SWIFT_NAME(view(path:));
65
+ - (void)clear NS_SWIFT_NAME(clear());
66
+
67
+ @end
68
+
69
+ NS_ASSUME_NONNULL_END
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",
package/pyproject.toml CHANGED
@@ -10,15 +10,15 @@ filterwarnings = ["error"]
10
10
 
11
11
  [tool.cibuildwheel]
12
12
  build-verbosity = 0
13
+
14
+ skip = ["*musllinux*", "*i686*", "pp*"]
15
+
16
+ [tool.cibuildwheel.linux]
13
17
  before-build = [
14
18
  "rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps",
15
19
  "mkdir -p build/usearch",
16
20
  "git submodule update --init --recursive"
17
21
  ]
18
-
19
- skip = ["*musllinux*", "*i686*", "pp*"]
20
-
21
- [tool.cibuildwheel.linux]
22
22
  manylinux-x86_64-image = "manylinux_2_28"
23
23
  manylinux-aarch64-image = "manylinux_2_28"
24
24
 
@@ -29,5 +29,19 @@ before-all = [
29
29
  repair-wheel-command = "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
30
30
 
31
31
  [tool.cibuildwheel.macos]
32
+ before-build = [
33
+ "rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps",
34
+ "mkdir -p build/usearch",
35
+ "git submodule update --init --recursive"
36
+ ]
32
37
  archs = ["x86_64", "universal2", "arm64"]
33
38
  repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
39
+
40
+
41
+ [tool.cibuildwheel.windows]
42
+ before-build = [
43
+ "rd /s /q {project}\\CMakeCache.txt {project}\\build {project}\\build_debug {project}\\CMakeFiles.txt {project}\\_deps || echo Done",
44
+ "md build\\usearch",
45
+ "git submodule update --init --recursive"
46
+ ]
47
+ archs = ["x86", "AMD64"]
package/python/lib.cpp CHANGED
@@ -137,7 +137,7 @@ static void add_one_to_index(native_index_t& index, label_t label, py::buffer ve
137
137
 
138
138
  // https://docs.python.org/3/library/struct.html#format-characters
139
139
  if (vector_info.format == "e")
140
- index.add(label, reinterpret_cast<f16_converted_t const*>(vector_data), 0, copy);
140
+ index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), 0, copy);
141
141
  else if (vector_info.format == "f")
142
142
  index.add(label, reinterpret_cast<float const*>(vector_data), 0, copy);
143
143
  else if (vector_info.format == "d")
@@ -179,8 +179,8 @@ static void add_many_to_index(native_index_t& index, py::buffer labels, py::buff
179
179
  if (vectors_info.format == "e")
180
180
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
181
181
  label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
182
- f16_converted_t const* vector =
183
- reinterpret_cast<f16_converted_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
182
+ f16_bits_t const* vector =
183
+ reinterpret_cast<f16_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
184
184
  index.add(label, vector, thread_idx, copy);
185
185
  });
186
186
  else if (vectors_info.format == "f")
@@ -216,7 +216,7 @@ static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, s
216
216
  // https://docs.python.org/3/library/struct.html#format-characters
217
217
  if (vector_info.format == "e")
218
218
  count = index.search( //
219
- reinterpret_cast<f16_converted_t const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
219
+ reinterpret_cast<f16_bits_t const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
220
220
  else if (vector_info.format == "f")
221
221
  count = index.search( //
222
222
  reinterpret_cast<float const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
@@ -272,7 +272,7 @@ static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors,
272
272
  // https://docs.python.org/3/library/struct.html#format-characters
273
273
  if (vectors_info.format == "e")
274
274
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
275
- f16_converted_t const* vector = (f16_converted_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
275
+ f16_bits_t const* vector = (f16_bits_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
276
276
  counts_py1d(task_idx) = static_cast<Py_ssize_t>(
277
277
  index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), thread_idx));
278
278
  });
@@ -371,7 +371,7 @@ void hash_buffer(hash_index_w_meta_t& index, py::buffer vector) {
371
371
  throw std::invalid_argument("Array elements must be 16, 32, or 64 bit hashable integers!");
372
372
  }
373
373
 
374
- PYBIND11_MODULE(usearch, m) {
374
+ PYBIND11_MODULE(index, m) {
375
375
  m.doc() = "Unum USearch Python bindings";
376
376
 
377
377
  auto i = py::class_<native_index_t>(m, "Index");
package/python/test.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import numpy as np
2
2
 
3
- import usearch
4
-
3
+ from usearch.index import Index, SetsIndex
5
4
  from numba import cfunc, types, carray
6
5
 
7
6
  # Showcases how to use Numba to JIT-compile similarity measures for USearch.
@@ -29,13 +28,13 @@ vectors = np.random.uniform(
29
28
  0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
30
29
  labels = np.array(range(count_vectors), dtype=np.longlong)
31
30
 
32
- index_udf = usearch.Index(ndim=96, metric_pointer=python_dot.address)
31
+ index_udf = Index(ndim=96, metric_pointer=python_dot.address)
33
32
  index_udf.add(labels, vectors, copy=True)
34
33
  results = index_udf.search(vectors, 10)
35
34
  print('found', results[0].shape, results[1].shape, results[2].shape)
36
35
 
37
36
 
38
- index = usearch.Index(ndim=96)
37
+ index = Index(ndim=96)
39
38
  index.add(labels, vectors, copy=True)
40
39
  assert index.ndim == count_dimensions
41
40
  assert index.capacity >= count_vectors
@@ -45,7 +44,7 @@ results = index.search(vectors, 10)
45
44
  print('found', results[0].shape, results[1].shape, results[2].shape)
46
45
 
47
46
 
48
- index_sets = usearch.SetsIndex()
47
+ index_sets = SetsIndex()
49
48
  index_sets.add(10, np.array([10, 12, 15], dtype=np.uint32))
50
49
  index_sets.add(11, np.array([11, 12, 15, 16], dtype=np.uint32))
51
50
  results = index_sets.search(np.array([12, 15], dtype=np.uint32), 20)
File without changes
package/rust/lib.cpp CHANGED
@@ -24,9 +24,20 @@ Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, s
24
24
  return results;
25
25
  }
26
26
 
27
- void Index::add(uint32_t label, rust::Slice<float const> vector) const { return add_in_thread(label, vector, 0); }
27
+ void Index::add(uint32_t label, rust::Slice<float const> vector) const {
28
+ index_->add(label, vector.data()); //
29
+ }
30
+
28
31
  Matches Index::search(rust::Slice<float const> vector, size_t count) const {
29
- return search_in_thread(vector, count, 0);
32
+ Matches results;
33
+ results.labels.reserve(count);
34
+ results.distances.reserve(count);
35
+ for (size_t i = 0; i != count; ++i)
36
+ results.labels.push_back(0), results.distances.push_back(0);
37
+ results.count = index_->search(vector.data(), count, results.labels.data(), results.distances.data());
38
+ results.labels.truncate(results.count);
39
+ results.distances.truncate(results.count);
40
+ return results;
30
41
  }
31
42
 
32
43
  void Index::reserve(size_t capacity) const { index_->reserve(capacity); }
package/setup.py CHANGED
@@ -9,11 +9,15 @@ compile_args = ['-std=c++11', '-O3']
9
9
  if sys.platform == 'darwin':
10
10
  compile_args.append('-mmacosx-version-min=10.13')
11
11
 
12
+ if sys.platform == 'win32':
13
+ compile_args = ['/std:c++14', '/O2']
14
+
15
+
12
16
  ext_modules = [
13
17
  Pybind11Extension(
14
- 'usearch',
18
+ 'usearch.index',
15
19
  ['python/lib.cpp'],
16
- extra_compile_args=compile_args,
20
+ extra_compile_args=compile_args
17
21
  ),
18
22
  ]
19
23
 
@@ -30,7 +34,8 @@ setup(
30
34
 
31
35
  name=__lib_name__,
32
36
  version=__version__,
33
- packages=find_packages(),
37
+ packages=['usearch'],
38
+ package_dir={'usearch': 'python/usearch'},
34
39
 
35
40
  description='Smaller & Faster Single-File Vector Search Engine from Unum',
36
41
  long_description=long_description,
@@ -255,6 +255,21 @@ inline static simsimd_f32_t simsimd_dot_f32x4avx2(simsimd_f32_t const* a, simsim
255
255
  #endif
256
256
  }
257
257
 
258
+ inline static simsimd_f32_t simsimd_dot_f16x16avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
259
+ #if defined(__AVX512F__)
260
+ __m512 ab_vec = _mm512_set1_ps(0);
261
+ for (size_t i = 0; i != d; i += 16)
262
+ ab_vec = _mm512_fmadd_ps( //
263
+ _mm512_cvtxph_ps(_mm256_loadu_ph(a + i)), //
264
+ _mm512_cvtxph_ps(_mm256_loadu_ph(b + i)), //
265
+ ab_vec);
266
+ return _mm512_reduce_add_ps(ab_vec);
267
+ #else
268
+ (void)a, (void)b, (void)d;
269
+ return 0;
270
+ #endif
271
+ }
272
+
258
273
  inline static int32_t simsimd_dot_i8x16avx2(int8_t const* a, int8_t const* b, size_t d) {
259
274
  #if defined(__AVX2__)
260
275
  __m256i ab_vec = _mm256_set1_epi32(0);