usearch 0.1.9 → 0.2.0

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.
package/python/test.py CHANGED
@@ -2,14 +2,40 @@ import numpy as np
2
2
 
3
3
  import usearch
4
4
 
5
+ from numba import cfunc, types, carray
6
+
7
+ # Showcases how to use Numba to JIT-compile similarity measures for USearch.
8
+ # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
9
+
10
+ signature = types.float32(
11
+ types.CPointer(types.float32),
12
+ types.CPointer(types.float32),
13
+ types.size_t, types.size_t)
14
+
15
+
16
+ @cfunc(signature)
17
+ def python_dot(a, b, n, m):
18
+ a_array = carray(a, n)
19
+ b_array = carray(b, n)
20
+ c = 0.0
21
+ for i in range(n):
22
+ c += a_array[i] * b_array[i]
23
+ return c
24
+
25
+
5
26
  count_vectors = 100
6
27
  count_dimensions = 96
7
-
8
- index = usearch.Index(dim=96)
9
28
  vectors = np.random.uniform(
10
29
  0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
11
30
  labels = np.array(range(count_vectors), dtype=np.longlong)
12
31
 
32
+ index_udf = usearch.Index(ndim=96, metric_pointer=python_dot.address)
33
+ index_udf.add(labels, vectors, copy=True)
34
+ results = index_udf.search(vectors, 10)
35
+ print('found', results[0].shape, results[1].shape, results[2].shape)
36
+
37
+
38
+ index = usearch.Index(ndim=96)
13
39
  index.add(labels, vectors, copy=True)
14
40
  assert index.ndim == count_dimensions
15
41
  assert index.capacity >= count_vectors
@@ -17,3 +43,10 @@ assert len(index) == count_vectors
17
43
 
18
44
  results = index.search(vectors, 10)
19
45
  print('found', results[0].shape, results[1].shape, results[2].shape)
46
+
47
+
48
+ index_sets = usearch.SetsIndex()
49
+ index_sets.add(10, np.array([10, 12, 15], dtype=np.uint32))
50
+ index_sets.add(11, np.array([11, 12, 15, 16], dtype=np.uint32))
51
+ results = index_sets.search(np.array([12, 15], dtype=np.uint32), 20)
52
+ print(results)
@@ -0,0 +1,34 @@
1
+ import numpy as np
2
+ from ucall.client import Client
3
+
4
+
5
+ class IndexClient:
6
+
7
+ def __init__(self, uri: str = '127.0.0.1', port: int = 8545, use_http: bool = True) -> None:
8
+ self.client = Client(uri=uri, port=port, use_http=use_http)
9
+
10
+ def add(self, labels: np.array, vectors: np.array):
11
+ if isinstance(labels, int):
12
+ return self.client.add_one(label=labels, vectors=vectors)
13
+ else:
14
+ return self.client.add_many(labels=labels, vectors=vectors)
15
+
16
+ def search(self, vectors: np.array, count: int) -> tuple[np.array, np.array, np.array]:
17
+ matches = []
18
+ distances = []
19
+ counts = []
20
+ # return self.client.search_one(vectors=vectors, count=count)
21
+ return matches, distances, counts
22
+
23
+ def __len__(self):
24
+ return self.client.size()
25
+
26
+ @property
27
+ def ndim(self):
28
+ return self.client.ndim()
29
+
30
+ def capacity(self):
31
+ return self.client.capacity()
32
+
33
+ def connectivity(self):
34
+ return self.client.connectivity()
@@ -0,0 +1,67 @@
1
+ import struct
2
+ import numpy as np
3
+
4
+
5
+ def load_matrix(filename: str, start_row: int = 0, count_rows: int = None, view: bool = False):
6
+ """
7
+ Read *.ibin, *.hbin, *.fbin, *.dbin files with matrixes.
8
+ Args:
9
+ :param filename (str): path to the matrix file
10
+ :param start_row (int): start reading vectors from this index
11
+ :param count_rows (int): number of vectors to read. If None, read all vectors
12
+ Returns:
13
+ Parsed matrix (numpy.ndarray)
14
+ """
15
+ dtype = np.float32
16
+ scalar_size = 4
17
+ if filename.endswith('.fbin'):
18
+ dtype = np.float32
19
+ scalar_size = 4
20
+ elif filename.endswith('.dbin'):
21
+ dtype = np.float64
22
+ scalar_size = 8
23
+ elif filename.endswith('.hbin'):
24
+ dtype = np.float16
25
+ scalar_size = 2
26
+ elif filename.endswith('.ibin'):
27
+ dtype = np.int32
28
+ scalar_size = 4
29
+ else:
30
+ raise Exception('Unknown file type')
31
+
32
+ with open(filename, 'rb') as f:
33
+ rows, cols = np.fromfile(f, count=2, dtype=np.int32)
34
+ rows = (rows - start_row) if count_rows is None else count_rows
35
+ row_offset = start_row * scalar_size * cols
36
+
37
+ if view:
38
+ return np.memmap(f, dtype=dtype, mode='r', offset=8+row_offset, shape=(rows, cols))
39
+ else:
40
+ return np.fromfile(f, count=rows * cols, dtype=dtype, offset=row_offset).reshape(rows, cols)
41
+
42
+
43
+ def save_matrix(vecs: np.array, filename: str):
44
+ """
45
+ Write *.ibin, *.hbin, *.fbin, *.dbin files with matrixes.
46
+ Args:
47
+ :param vecs (numpy.array): the matrix to serialize
48
+ :param filename (str): path to the matrix file
49
+ """
50
+ dtype = np.float32
51
+ if filename.endswith('.fbin'):
52
+ dtype = np.float32
53
+ elif filename.endswith('.dbin'):
54
+ dtype = np.float64
55
+ elif filename.endswith('.hbin'):
56
+ dtype = np.float16
57
+ elif filename.endswith('.ibin'):
58
+ dtype = np.int32
59
+ else:
60
+ raise Exception('Unknown file type')
61
+
62
+ assert len(vecs.shape) == 2, 'Input array must have 2 dimensions'
63
+ with open(filename, 'wb') as f:
64
+ nvecs, dim = vecs.shape
65
+ f.write(struct.pack('<i', nvecs))
66
+ f.write(struct.pack('<i', dim))
67
+ vecs.astype(dtype).flatten().tofile(f)
@@ -0,0 +1,51 @@
1
+ import ucall.rich_posix as ucall
2
+ import usearch
3
+
4
+ import fire
5
+ import numpy as np
6
+ from PIL import Image
7
+
8
+
9
+ def serve(ndim: int, metric: str = 'ip'):
10
+
11
+ server = ucall.Server()
12
+ index = usearch.Index(ndim=ndim, metric=metric)
13
+
14
+ @server
15
+ def add_one(label: int, vector: np.array):
16
+ labels = np.array([label], dtype=np.longlong)
17
+ vectors = vector.reshape(vector.shape[0], 1)
18
+ index.add(labels, vectors, copy=True)
19
+
20
+ @server
21
+ def add_many(labels: np.array, vectors: np.array):
22
+ labels = labels.astype(np.longlong)
23
+ index.add(labels, vectors, copy=True)
24
+
25
+ @server
26
+ def search_one(vector: np.array, count: int) -> np.ndarray:
27
+ vectors = vector.reshape(vector.shape[0], 1)
28
+ results = index.search(vectors, 3)
29
+ return results[0][:results[2][0]]
30
+
31
+ @server
32
+ def size() -> int:
33
+ return len(index)
34
+
35
+ @server
36
+ def ndim() -> int:
37
+ return index.ndim
38
+
39
+ @server
40
+ def capacity() -> int:
41
+ return index.capacity()
42
+
43
+ @server
44
+ def connectivity() -> int:
45
+ return index.connectivity()
46
+
47
+ server.run()
48
+
49
+
50
+ if __name__ == '__main__':
51
+ fire(serve)
package/setup.py CHANGED
@@ -32,7 +32,7 @@ setup(
32
32
  version=__version__,
33
33
  packages=find_packages(),
34
34
 
35
- description='The Mighty Tiny Vector Search Engine with Automatic Quantization and Hardware Acceleration',
35
+ description='Smaller & Faster Single-File Vector Search Engine from Unum',
36
36
  long_description=long_description,
37
37
  long_description_content_type='text/markdown',
38
38
  license='Apache-2.0',
@@ -62,12 +62,14 @@
62
62
  "stop_token": "cpp",
63
63
  "streambuf": "cpp",
64
64
  "thread": "cpp",
65
- "typeinfo": "cpp"
65
+ "typeinfo": "cpp",
66
+ "complex": "cpp"
66
67
  },
67
68
  "cSpell.words": [
68
69
  "dtype",
69
70
  "dtypes",
70
71
  "kann",
72
+ "simsimd",
71
73
  "unsw"
72
74
  ]
73
75
  }
@@ -70,7 +70,10 @@ inline static simsimd_f16_t simsimd_dot_f16sve(simsimd_f16_t const* a_enum, sims
70
70
  i += svcnth();
71
71
  pg_vec = svwhilelt_b16(i, d);
72
72
  } while (svptest_any(svptrue_b16(), pg_vec));
73
- return static_cast<simsimd_f16_t>(svaddv_f16(svptrue_b16(), ab_vec));
73
+ float16_t f16 = svaddv_f16(svptrue_b16(), ab_vec);
74
+ simsimd_f16_t i16;
75
+ memcpy(&i16, &f16, sizeof(i16));
76
+ return i16;
74
77
  #else
75
78
  (void)a_enum, (void)b_enum, (void)d;
76
79
  return 0;
@@ -103,7 +106,7 @@ inline static simsimd_f32_t simsimd_cos_f32sve(simsimd_f32_t const* a, simsimd_f
103
106
  #endif
104
107
  }
105
108
 
106
- inline static simsimd_f32_t simsimd_euclidean_f32sve(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
109
+ inline static simsimd_f32_t simsimd_l2sq_f32sve(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
107
110
  #if defined(__ARM_FEATURE_SVE)
108
111
  size_t i = 0;
109
112
  svfloat32_t d2_vec = svdupq_n_f32(0.f, 0.f, 0.f, 0.f);
@@ -124,8 +127,7 @@ inline static simsimd_f32_t simsimd_euclidean_f32sve(simsimd_f32_t const* a, sim
124
127
  #endif
125
128
  }
126
129
 
127
- inline static simsimd_f16_t simsimd_euclidean_f16sve( //
128
- simsimd_f16_t const* a_enum, simsimd_f16_t const* b_enum, size_t d) {
130
+ inline static simsimd_f16_t simsimd_l2sq_f16sve(simsimd_f16_t const* a_enum, simsimd_f16_t const* b_enum, size_t d) {
129
131
  #if defined(__ARM_FEATURE_SVE)
130
132
  size_t i = 0;
131
133
  svfloat16_t d2_vec = svdupq_n_f16(0, 0, 0, 0, 0, 0, 0, 0);
@@ -140,8 +142,10 @@ inline static simsimd_f16_t simsimd_euclidean_f16sve( //
140
142
  i += svcnth();
141
143
  pg_vec = svwhilelt_b16(i, d);
142
144
  } while (svptest_any(svptrue_b16(), pg_vec));
143
- simsimd_f16_t d2 = svaddv_f16(svptrue_b16(), d2_vec);
144
- return static_cast<simsimd_f16_t>(sqrt(d2));
145
+ float16_t f16 = svaddv_f16(svptrue_b16(), d2_vec);
146
+ simsimd_f16_t i16;
147
+ memcpy(&i16, &f16, sizeof(i16));
148
+ return i16;
145
149
  #else
146
150
  (void)a_enum, (void)b_enum, (void)d;
147
151
  return 0;
@@ -186,7 +190,10 @@ inline static simsimd_f16_t simsimd_dot_f16x8neon(simsimd_f16_t const* a, simsim
186
190
  float16x8_t ab_vec = vdupq_n_f16(0);
187
191
  for (size_t i = 0; i != d; i += 8)
188
192
  ab_vec = vaddq_f16(ab_vec, vmulq_f16(vld1q_f16((float16_t const*)a + i), vld1q_f16((float16_t const*)b + i)));
189
- return vaddvq_f32(vcvt_f32_f16(vget_high_f16(ab_vec))) + vaddvq_f32(vcvt_f32_f16(vget_low_f16(ab_vec)));
193
+ float16_t f16 = vaddvq_f32(vcvt_f32_f16(vget_high_f16(ab_vec))) + vaddvq_f32(vcvt_f32_f16(vget_low_f16(ab_vec)));
194
+ simsimd_f16_t i16;
195
+ memcpy(&i16, &f16, sizeof(i16));
196
+ return i16;
190
197
  #else
191
198
  (void)a, (void)b, (void)d;
192
199
  return 0;
package/src/advanced.hpp CHANGED
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
  #include <functional>
3
+ #include <stdexcept> // `std::invalid_argument`
3
4
  #include <thread>
4
5
  #include <vector>
5
6
 
@@ -7,8 +8,14 @@
7
8
  #include <sys/auxv.h>
8
9
  #endif
9
10
 
11
+ #if defined(__aarch64__)
12
+ #include <arm_fp16.h>
13
+ #endif
14
+
10
15
  #include <fp16/fp16.h>
16
+ #if defined(USEARCH_USE_SIMD)
11
17
  #include <simsimd/simsimd.h>
18
+ #endif
12
19
 
13
20
  #include <usearch/usearch.hpp>
14
21
 
@@ -39,6 +46,27 @@ template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
39
46
  class i8q100_converted_t;
40
47
  class f16_converted_t;
41
48
 
49
+ inline float f16_to_f32(std::uint16_t u16) noexcept {
50
+ #if defined(__aarch64__)
51
+ __fp16 f16;
52
+ std::memcpy(&f16, &u16, sizeof(std::uint16_t));
53
+ return float(f16);
54
+ #else
55
+ return fp16_ieee_to_fp32_value(u16);
56
+ #endif
57
+ }
58
+
59
+ inline std::uint16_t f32_to_f16(float f32) noexcept {
60
+ #if defined(__aarch64__)
61
+ __fp16 f16 = __fp16(f32);
62
+ std::uint16_t u16;
63
+ std::memcpy(&u16, &f16, sizeof(std::uint16_t));
64
+ return u16;
65
+ #else
66
+ return fp16_ieee_from_fp32_value(f32);
67
+ #endif
68
+ }
69
+
42
70
  class f16_converted_t {
43
71
  std::uint16_t uint16_{};
44
72
 
@@ -49,11 +77,11 @@ class f16_converted_t {
49
77
  inline f16_converted_t(f16_converted_t const&) = default;
50
78
  inline f16_converted_t& operator=(f16_converted_t const&) = default;
51
79
 
52
- inline operator float() const noexcept { return fp16_ieee_to_fp32_value(uint16_); }
80
+ inline operator float() const noexcept { return f16_to_f32(uint16_); }
53
81
 
54
82
  inline f16_converted_t(i8q100_converted_t) noexcept;
55
- inline f16_converted_t(float v) noexcept : uint16_(fp16_ieee_from_fp32_value(v)) {}
56
- inline f16_converted_t(double v) noexcept : uint16_(fp16_ieee_from_fp32_value(v)) {}
83
+ inline f16_converted_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
84
+ inline f16_converted_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
57
85
 
58
86
  inline f16_converted_t operator+(f16_converted_t other) const noexcept { return {float(*this) + float(other)}; }
59
87
  inline f16_converted_t operator-(f16_converted_t other) const noexcept { return {float(*this) - float(other)}; }
@@ -69,22 +97,22 @@ class f16_converted_t {
69
97
  inline f16_converted_t operator/(double other) const noexcept { return {float(*this) / other}; }
70
98
 
71
99
  inline f16_converted_t& operator+=(float v) noexcept {
72
- uint16_ = fp16_ieee_from_fp32_value(v + fp16_ieee_to_fp32_value(uint16_));
100
+ uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
73
101
  return *this;
74
102
  }
75
103
 
76
104
  inline f16_converted_t& operator-=(float v) noexcept {
77
- uint16_ = fp16_ieee_from_fp32_value(v - fp16_ieee_to_fp32_value(uint16_));
105
+ uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
78
106
  return *this;
79
107
  }
80
108
 
81
109
  inline f16_converted_t& operator*=(float v) noexcept {
82
- uint16_ = fp16_ieee_from_fp32_value(v * fp16_ieee_to_fp32_value(uint16_));
110
+ uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
83
111
  return *this;
84
112
  }
85
113
 
86
114
  inline f16_converted_t& operator/=(float v) noexcept {
87
- uint16_ = fp16_ieee_from_fp32_value(v / fp16_ieee_to_fp32_value(uint16_));
115
+ uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
88
116
  return *this;
89
117
  }
90
118
  };
@@ -102,6 +130,10 @@ class i8q100_converted_t {
102
130
  inline operator float() const noexcept { return float(int8_) / 100.f; }
103
131
  inline operator f16_converted_t() const noexcept { return float(int8_) / 100.f; }
104
132
  inline operator double() const noexcept { return double(int8_) / 100.0; }
133
+ inline operator std::int8_t() const noexcept { return int8_; }
134
+ inline operator std::int16_t() const noexcept { return int8_; }
135
+ inline operator std::int32_t() const noexcept { return int8_; }
136
+ inline operator std::int64_t() const noexcept { return int8_; }
105
137
 
106
138
  inline i8q100_converted_t(float v) noexcept
107
139
  : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.f), -100, 100)) {}
@@ -127,7 +159,7 @@ void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callbac
127
159
  }
128
160
 
129
161
  std::vector<std::thread> threads_pool;
130
- std::size_t tasks_per_thread = tasks / threads + (tasks % threads) != 0;
162
+ std::size_t tasks_per_thread = (tasks / threads) + ((tasks % threads) != 0);
131
163
  for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
132
164
  threads_pool.emplace_back([=]() {
133
165
  for (std::size_t task_idx = thread_idx * tasks_per_thread;
@@ -198,7 +230,7 @@ inline accuracy_t accuracy_from_name(char const* name, std::size_t len) {
198
230
  else if (str_equals(name, len, "i8q100"))
199
231
  accuracy = accuracy_t::i8q100_k;
200
232
  else
201
- throw std::runtime_error("Unknown type, choose: f32, f16, f64, i8q100");
233
+ throw std::invalid_argument("Unknown type, choose: f32, f16, f64, i8q100");
202
234
  return accuracy;
203
235
  }
204
236
 
@@ -206,16 +238,24 @@ template <typename index_at>
206
238
  inline index_at index_from_name( //
207
239
  char const* name, std::size_t len, std::size_t dimensions, accuracy_t accuracy, config_t const& config) {
208
240
 
209
- if (str_equals(name, len, "l2_sq") || str_equals(name, len, "euclidean_sq"))
241
+ if (str_equals(name, len, "l2_sq") || str_equals(name, len, "euclidean_sq")) {
242
+ if (dimensions == 0)
243
+ throw std::invalid_argument("The number of dimensions must be positive");
210
244
  return index_at::l2(dimensions, accuracy, config);
211
- else if (str_equals(name, len, "ip") || str_equals(name, len, "inner") || str_equals(name, len, "dot"))
245
+ } else if (str_equals(name, len, "ip") || str_equals(name, len, "inner") || str_equals(name, len, "dot")) {
246
+ if (dimensions == 0)
247
+ throw std::invalid_argument("The number of dimensions must be positive");
212
248
  return index_at::ip(dimensions, accuracy, config);
213
- else if (str_equals(name, len, "cos") || str_equals(name, len, "angular"))
249
+ } else if (str_equals(name, len, "cos") || str_equals(name, len, "angular")) {
250
+ if (dimensions == 0)
251
+ throw std::invalid_argument("The number of dimensions must be positive");
214
252
  return index_at::cos(dimensions, accuracy, config);
215
- else if (str_equals(name, len, "haversine"))
253
+ } else if (str_equals(name, len, "haversine")) {
254
+ if (dimensions != 2 && dimensions != 0)
255
+ throw std::invalid_argument("The number of dimensions must be equal to two");
216
256
  return index_at::haversine(accuracy, config);
217
- else
218
- throw std::runtime_error("Unknown distance, choose: l2_sq, ip, cos, hamming, jaccard");
257
+ } else
258
+ throw std::invalid_argument("Unknown distance, choose: l2_sq, ip, cos, hamming, jaccard");
219
259
  return {};
220
260
  }
221
261
 
@@ -282,9 +322,25 @@ template <> struct cast_gt<f16_converted_t, f16_converted_t> {
282
322
  bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
283
323
  };
284
324
 
325
+ struct ip_i8q100_converted_t {
326
+ float operator()(i8q100_converted_t const* a, i8q100_converted_t const* b, std::size_t d) const noexcept {
327
+ std::int64_t ab = 0;
328
+ #if defined(__GNUC__)
329
+ #pragma GCC ivdep
330
+ #elif defined(__clang__)
331
+ #pragma clang loop vectorize(enable)
332
+ #elif defined(_OPENMP)
333
+ #pragma omp simd reduction(+ : ab)
334
+ #endif
335
+ for (std::size_t i = 0; i < d; ++i)
336
+ ab += std::int16_t(a[i]) * std::int16_t(b[i]);
337
+ return 1.f - ab / 10000.f;
338
+ }
339
+ };
340
+
285
341
  /**
286
342
  * @brief Oversimplified type-punned index for equidimensional floating-point
287
- * vectors with automatic down-casting and isa acceleration.
343
+ * vectors with automatic down-casting and hardware acceleration.
288
344
  */
289
345
  template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
290
346
  class auto_index_gt {
@@ -336,6 +392,7 @@ class auto_index_gt {
336
392
  std::size_t connectivity() const noexcept { return index_->connectivity(); }
337
393
  std::size_t size() const noexcept { return index_->size(); }
338
394
  std::size_t capacity() const noexcept { return index_->capacity(); }
395
+ void clear() noexcept { return index_->clear(); }
339
396
 
340
397
  isa_t acceleration() const noexcept { return acceleration_; }
341
398
  std::size_t concurrency() const noexcept {
@@ -362,6 +419,12 @@ class auto_index_gt {
362
419
  static auto_index_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return make(2, accuracy, haversine_metric(accuracy), make_casts(accuracy), config); }
363
420
  // clang-format on
364
421
 
422
+ static auto_index_gt udf( //
423
+ std::size_t dimensions, punned_stateful_metric_t metric, //
424
+ accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) {
425
+ return make(dimensions, accuracy, {metric, isa_t::auto_k}, make_casts(accuracy), config);
426
+ }
427
+
365
428
  auto_index_gt fork() const {
366
429
  auto_index_gt result;
367
430
 
@@ -456,6 +519,8 @@ class auto_index_gt {
456
519
  }
457
520
 
458
521
  static metric_and_meta_t ip_metric_f32(std::size_t dimensions) {
522
+ (void)dimensions;
523
+ #if defined(USEARCH_USE_SIMD)
459
524
  #if defined(__x86_64__)
460
525
  if (dimensions % 4 == 0)
461
526
  return {
@@ -479,11 +544,14 @@ class auto_index_gt {
479
544
  }),
480
545
  isa_t::neon_k,
481
546
  };
547
+ #endif
482
548
  #endif
483
549
  return {pun_metric<f32_t>(ip_gt<f32_t>{}), isa_t::auto_k};
484
550
  }
485
551
 
486
552
  static metric_and_meta_t ip_metric_f16(std::size_t dimensions) {
553
+ (void)dimensions;
554
+ #if defined(USEARCH_USE_SIMD)
487
555
  #if defined(__x86_64__)
488
556
  #elif defined(__aarch64__)
489
557
  if (supports_arm_sve())
@@ -500,13 +568,14 @@ class auto_index_gt {
500
568
  }),
501
569
  isa_t::neon_k,
502
570
  };
571
+ #endif
503
572
  #endif
504
573
  return {pun_metric<f16_converted_t>(ip_gt<f16_converted_t>{}), isa_t::auto_k};
505
574
  }
506
575
 
507
576
  static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
508
577
  switch (accuracy) {
509
- case accuracy_t::i8q100_k: return {};
578
+ case accuracy_t::i8q100_k: return {pun_metric<i8q100_converted_t>(ip_i8q100_converted_t{}), isa_t::auto_k};
510
579
  case accuracy_t::f16_k: return ip_metric_f16(dimensions);
511
580
  case accuracy_t::f32_k: return ip_metric_f32(dimensions);
512
581
  case accuracy_t::f64_k: return {pun_metric<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
package/src/bench.cpp CHANGED
@@ -26,6 +26,8 @@
26
26
  using namespace unum::usearch;
27
27
  using namespace unum;
28
28
 
29
+ using vector_id_t = std::uint32_t;
30
+
29
31
  template <typename element_at>
30
32
  std::size_t offset_of(element_at const* begin, element_at const* end, element_at v) noexcept {
31
33
  auto iterator = begin;
@@ -344,6 +346,46 @@ void run_big_or_small(dataset_at& dataset, args_t const& args, config_t config)
344
346
  run_type_punned<auto_index_gt<std::size_t, neighbor_id_at>>(dataset, args, config);
345
347
  }
346
348
 
349
+ void report_alternative_setups() {
350
+ using set_member_t = std::uint32_t;
351
+ using sets_index_t = index_gt<jaccard_gt<set_member_t>, std::size_t, std::uint32_t, set_member_t>;
352
+ set_member_t set_a[] = {10, 12, 15};
353
+ set_member_t set_b[] = {11, 12, 15, 16};
354
+ sets_index_t index;
355
+ index.reserve(100);
356
+ index.add(10, span_gt<set_member_t const>{&set_a[0], 3});
357
+ index.add(11, span_gt<set_member_t const>{&set_b[0], 4});
358
+ exit(0);
359
+ }
360
+
361
+ void report_expected_losses(persisted_dataset_gt<float, vector_id_t> const& dataset) {
362
+
363
+ auto vec1 = dataset.vector(0);
364
+ auto vec2 = dataset.query(0);
365
+ std::vector<f16_converted_t> vec1f16(dataset.dimensions());
366
+ std::vector<f16_converted_t> vec2f16(dataset.dimensions());
367
+ std::transform(vec1, vec1 + dataset.dimensions(), vec1f16.data(), [](float v) { return v; });
368
+ std::transform(vec2, vec2 + dataset.dimensions(), vec2f16.data(), [](float v) { return v; });
369
+ std::vector<i8q100_converted_t> vec1i8(dataset.dimensions());
370
+ std::vector<i8q100_converted_t> vec2i8(dataset.dimensions());
371
+ std::transform(vec1, vec1 + dataset.dimensions(), vec1i8.data(), [](float v) { return v; });
372
+ std::transform(vec2, vec2 + dataset.dimensions(), vec2i8.data(), [](float v) { return v; });
373
+
374
+ #if 0
375
+ auto ip_default = ip_gt<float>{}(vec1, vec2, dataset.dimensions());
376
+ auto ip_sve = 1.f - simsimd_dot_f32sve(vec1, vec2, dataset.dimensions());
377
+ auto ip_neon = 1.f - simsimd_dot_f32x4neon(vec1, vec2, dataset.dimensions());
378
+ auto ip_f16 = ip_gt<f16_converted_t>{}(vec1f16.data(), vec2f16.data(), dataset.dimensions());
379
+ auto ip_f16sve = 1.f - simsimd_dot_f16sve((simsimd_f16_t const*)vec1f16.data(),
380
+ (simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
381
+ auto ip_f16neon = 1.f - simsimd_dot_f16x8neon((simsimd_f16_t const*)vec1f16.data(),
382
+ (simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
383
+ auto ip_i8 = ip_i8q100_converted_t{}(vec1i8.data(), vec2i8.data(), dataset.dimensions());
384
+
385
+ exit(0);
386
+ #endif
387
+ }
388
+
347
389
  int main(int argc, char** argv) {
348
390
 
349
391
  // Print backtrace if something goes wrong.
@@ -393,8 +435,6 @@ int main(int argc, char** argv) {
393
435
  std::printf("-- Query vectors path: %s\n", args.path_queries.c_str());
394
436
  std::printf("-- Ground truth neighbors path: %s\n", args.path_neighbors.c_str());
395
437
 
396
- using vector_id_t = std::uint32_t;
397
-
398
438
  persisted_dataset_gt<float, vector_id_t> dataset{
399
439
  args.path_vectors.c_str(),
400
440
  args.path_queries.c_str(),
@@ -405,6 +445,9 @@ int main(int argc, char** argv) {
405
445
  std::printf("-- Queries count: %zu\n", dataset.queries_count());
406
446
  std::printf("-- Neighbors per query: %zu\n", dataset.neighborhood_size());
407
447
 
448
+ report_alternative_setups();
449
+ report_expected_losses(dataset);
450
+
408
451
  config_t config;
409
452
  config.connectivity = args.connectivity;
410
453
  config.expansion_add = args.expansion_add;
@@ -0,0 +1,17 @@
1
+
2
+ if(NOT WOLFRAM_PATH)
3
+ set(WOLFRAM_PATH "/usr/local/Wolfram/Mathematica")
4
+ endif()
5
+
6
+ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${WOLFRAM_PATH}/SystemFiles/Links/WSTP/DeveloperKit/Linux-x86-64/CompilerAdditions/")
7
+ include(WSTP)
8
+ include_directories("${WOLFRAM_PATH}/SystemFiles/IncludeFiles/C/")
9
+ link_directories("${WOLFRAM_PATH}/SystemFiles/Libraries/Linux-x86-64/")
10
+
11
+ add_library(usearchWFM SHARED lib.cpp)
12
+
13
+ # Link against the Wolfram LibraryLink library
14
+ target_link_libraries(usearchWFM WSTP::STATIC_LIBRARY)
15
+
16
+ # Set the output name of the library to be compatible with Wolfram Language
17
+ set_target_properties(usearchWFM PROPERTIES PREFIX "" OUTPUT_NAME "usearchWFM")