usearch 0.1.8 → 0.1.10

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,19 +2,44 @@ import numpy as np
2
2
 
3
3
  import usearch
4
4
 
5
- count_vectors = 100
6
- count_dimensions = 96
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
7
24
 
8
- index = usearch.Index(dim=96)
9
- print(index)
10
25
 
26
+ count_vectors = 100
27
+ count_dimensions = 96
11
28
  vectors = np.random.uniform(
12
29
  0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
13
30
  labels = np.array(range(count_vectors), dtype=np.longlong)
14
- print('will insert', labels.shape, vectors.shape)
15
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)
16
39
  index.add(labels, vectors, copy=True)
40
+ assert index.ndim == count_dimensions
41
+ assert index.capacity >= count_vectors
42
+ assert len(index) == count_vectors
17
43
 
18
- print('will search')
19
44
  results = index.search(vectors, 10)
20
45
  print('found', results[0].shape, results[1].shape, results[2].shape)
@@ -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
@@ -7,8 +7,14 @@
7
7
  #include <sys/auxv.h>
8
8
  #endif
9
9
 
10
+ #if defined(__aarch64__)
11
+ #include <arm_fp16.h>
12
+ #endif
13
+
10
14
  #include <fp16/fp16.h>
15
+ #if defined(USEARCH_USE_SIMD)
11
16
  #include <simsimd/simsimd.h>
17
+ #endif
12
18
 
13
19
  #include <usearch/usearch.hpp>
14
20
 
@@ -39,6 +45,27 @@ template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
39
45
  class i8q100_converted_t;
40
46
  class f16_converted_t;
41
47
 
48
+ inline float f16_to_f32(std::uint16_t u16) noexcept {
49
+ #if defined(__aarch64__)
50
+ __fp16 f16;
51
+ std::memcpy(&f16, &u16, sizeof(std::uint16_t));
52
+ return float(f16);
53
+ #else
54
+ return fp16_ieee_to_fp32_value(u16);
55
+ #endif
56
+ }
57
+
58
+ inline std::uint16_t f32_to_f16(float f32) noexcept {
59
+ #if defined(__aarch64__)
60
+ __fp16 f16 = __fp16(f32);
61
+ std::uint16_t u16;
62
+ std::memcpy(&u16, &f16, sizeof(std::uint16_t));
63
+ return u16;
64
+ #else
65
+ return fp16_ieee_from_fp32_value(f32);
66
+ #endif
67
+ }
68
+
42
69
  class f16_converted_t {
43
70
  std::uint16_t uint16_{};
44
71
 
@@ -49,11 +76,11 @@ class f16_converted_t {
49
76
  inline f16_converted_t(f16_converted_t const&) = default;
50
77
  inline f16_converted_t& operator=(f16_converted_t const&) = default;
51
78
 
52
- inline operator float() const noexcept { return fp16_ieee_to_fp32_value(uint16_); }
79
+ inline operator float() const noexcept { return f16_to_f32(uint16_); }
53
80
 
54
81
  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)) {}
82
+ inline f16_converted_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
83
+ inline f16_converted_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
57
84
 
58
85
  inline f16_converted_t operator+(f16_converted_t other) const noexcept { return {float(*this) + float(other)}; }
59
86
  inline f16_converted_t operator-(f16_converted_t other) const noexcept { return {float(*this) - float(other)}; }
@@ -69,22 +96,22 @@ class f16_converted_t {
69
96
  inline f16_converted_t operator/(double other) const noexcept { return {float(*this) / other}; }
70
97
 
71
98
  inline f16_converted_t& operator+=(float v) noexcept {
72
- uint16_ = fp16_ieee_from_fp32_value(v + fp16_ieee_to_fp32_value(uint16_));
99
+ uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
73
100
  return *this;
74
101
  }
75
102
 
76
103
  inline f16_converted_t& operator-=(float v) noexcept {
77
- uint16_ = fp16_ieee_from_fp32_value(v - fp16_ieee_to_fp32_value(uint16_));
104
+ uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
78
105
  return *this;
79
106
  }
80
107
 
81
108
  inline f16_converted_t& operator*=(float v) noexcept {
82
- uint16_ = fp16_ieee_from_fp32_value(v * fp16_ieee_to_fp32_value(uint16_));
109
+ uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
83
110
  return *this;
84
111
  }
85
112
 
86
113
  inline f16_converted_t& operator/=(float v) noexcept {
87
- uint16_ = fp16_ieee_from_fp32_value(v / fp16_ieee_to_fp32_value(uint16_));
114
+ uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
88
115
  return *this;
89
116
  }
90
117
  };
@@ -102,6 +129,10 @@ class i8q100_converted_t {
102
129
  inline operator float() const noexcept { return float(int8_) / 100.f; }
103
130
  inline operator f16_converted_t() const noexcept { return float(int8_) / 100.f; }
104
131
  inline operator double() const noexcept { return double(int8_) / 100.0; }
132
+ inline operator std::int8_t() const noexcept { return int8_; }
133
+ inline operator std::int16_t() const noexcept { return int8_; }
134
+ inline operator std::int32_t() const noexcept { return int8_; }
135
+ inline operator std::int64_t() const noexcept { return int8_; }
105
136
 
106
137
  inline i8q100_converted_t(float v) noexcept
107
138
  : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.f), -100, 100)) {}
@@ -127,17 +158,17 @@ void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callbac
127
158
  }
128
159
 
129
160
  std::vector<std::thread> threads_pool;
130
- std::size_t tasks_per_thread = threads / tasks + (threads % tasks) != 0;
131
- for (std::size_t thread = 0; thread != threads; ++thread) {
161
+ std::size_t tasks_per_thread = (tasks / threads) + ((tasks % threads) != 0);
162
+ for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
132
163
  threads_pool.emplace_back([=]() {
133
- for (std::size_t task_idx = thread * tasks_per_thread;
134
- task_idx < std::min(tasks, thread * tasks_per_thread + tasks_per_thread); ++task_idx)
135
- callback(thread, task_idx);
164
+ for (std::size_t task_idx = thread_idx * tasks_per_thread;
165
+ task_idx < std::min(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
166
+ callback(thread_idx, task_idx);
136
167
  });
137
168
  }
138
169
 
139
- for (std::size_t thread = 0; thread != threads; ++thread)
140
- threads_pool[thread].join();
170
+ for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx)
171
+ threads_pool[thread_idx].join();
141
172
  }
142
173
 
143
174
  /**
@@ -172,6 +203,16 @@ enum class accuracy_t {
172
203
  i8q100_k,
173
204
  };
174
205
 
206
+ inline char const* accuracy_name(accuracy_t accuracy) noexcept {
207
+ switch (accuracy) {
208
+ case accuracy_t::f32_k: return "f32";
209
+ case accuracy_t::f16_k: return "f16";
210
+ case accuracy_t::f64_k: return "f64";
211
+ case accuracy_t::i8q100_k: return "i8q100";
212
+ default: return "";
213
+ }
214
+ }
215
+
175
216
  inline bool str_equals(char const* begin, std::size_t len, char const* other_begin) noexcept {
176
217
  std::size_t other_len = strlen(other_begin);
177
218
  return len == other_len && strncmp(begin, other_begin, len) == 0;
@@ -217,13 +258,14 @@ enum class isa_t {
217
258
  avx512_k,
218
259
  };
219
260
 
220
- inline char const* isa(isa_t isa) noexcept {
261
+ inline char const* isa_name(isa_t isa) noexcept {
221
262
  switch (isa) {
222
263
  case isa_t::auto_k: return "auto";
223
264
  case isa_t::neon_k: return "neon";
224
265
  case isa_t::sve_k: return "sve";
225
266
  case isa_t::avx2_k: return "avx2";
226
267
  case isa_t::avx512_k: return "avx512";
268
+ default: return "";
227
269
  }
228
270
  }
229
271
 
@@ -271,9 +313,25 @@ template <> struct cast_gt<f16_converted_t, f16_converted_t> {
271
313
  bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
272
314
  };
273
315
 
316
+ struct ip_i8q100_converted_t {
317
+ float operator()(i8q100_converted_t const* a, i8q100_converted_t const* b, std::size_t d) const noexcept {
318
+ std::int64_t ab = 0;
319
+ #if defined(__GNUC__)
320
+ #pragma GCC ivdep
321
+ #elif defined(__clang__)
322
+ #pragma clang loop vectorize(enable)
323
+ #elif defined(_OPENMP)
324
+ #pragma omp simd reduction(+ : ab)
325
+ #endif
326
+ for (std::size_t i = 0; i < d; ++i)
327
+ ab += std::int16_t(a[i]) * std::int16_t(b[i]);
328
+ return 1.f - ab / 10000.f;
329
+ }
330
+ };
331
+
274
332
  /**
275
333
  * @brief Oversimplified type-punned index for equidimensional floating-point
276
- * vectors with automatic down-casting and isa acceleration.
334
+ * vectors with automatic down-casting and hardware acceleration.
277
335
  */
278
336
  template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
279
337
  class auto_index_gt {
@@ -351,6 +409,12 @@ class auto_index_gt {
351
409
  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); }
352
410
  // clang-format on
353
411
 
412
+ static auto_index_gt udf( //
413
+ std::size_t dimensions, punned_stateful_metric_t metric, //
414
+ accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) {
415
+ return make(dimensions, accuracy, {metric, isa_t::auto_k}, make_casts(accuracy), config);
416
+ }
417
+
354
418
  auto_index_gt fork() const {
355
419
  auto_index_gt result;
356
420
 
@@ -445,7 +509,8 @@ class auto_index_gt {
445
509
  }
446
510
 
447
511
  static metric_and_meta_t ip_metric_f32(std::size_t dimensions) {
448
- #if 0
512
+ (void)dimensions;
513
+ #if defined(USEARCH_USE_SIMD)
449
514
  #if defined(__x86_64__)
450
515
  if (dimensions % 4 == 0)
451
516
  return {
@@ -458,7 +523,7 @@ class auto_index_gt {
458
523
  if (supports_arm_sve())
459
524
  return {
460
525
  pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
461
- return 1 - simsimd_dot_f32sve(a, b, d);
526
+ return 1 - simsimd_dot_f32sve(a, b, d);
462
527
  }),
463
528
  isa_t::sve_k,
464
529
  };
@@ -475,7 +540,8 @@ class auto_index_gt {
475
540
  }
476
541
 
477
542
  static metric_and_meta_t ip_metric_f16(std::size_t dimensions) {
478
- #if 0
543
+ (void)dimensions;
544
+ #if defined(USEARCH_USE_SIMD)
479
545
  #if defined(__x86_64__)
480
546
  #elif defined(__aarch64__)
481
547
  if (supports_arm_sve())
@@ -499,7 +565,7 @@ class auto_index_gt {
499
565
 
500
566
  static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
501
567
  switch (accuracy) {
502
- case accuracy_t::i8q100_k: return {};
568
+ case accuracy_t::i8q100_k: return {pun_metric<i8q100_converted_t>(ip_i8q100_converted_t{}), isa_t::auto_k};
503
569
  case accuracy_t::f16_k: return ip_metric_f16(dimensions);
504
570
  case accuracy_t::f32_k: return ip_metric_f32(dimensions);
505
571
  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;
@@ -295,8 +297,10 @@ void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
295
297
  if (args.quantize_i8)
296
298
  accuracy = accuracy_t::i8q100_k;
297
299
 
300
+ std::printf("-- Accuracy: %s\n", accuracy_name(accuracy));
301
+
298
302
  index_at index{type_punned_index_for_metric<index_at>(dataset, args, config, accuracy)};
299
- std::printf("- Hardware acceleration: %s\n", isa(index.acceleration()));
303
+ std::printf("- Hardware acceleration: %s\n", isa_name(index.acceleration()));
300
304
  std::printf("-- Will benchmark in-memory\n");
301
305
 
302
306
  single_shot(dataset, index, true);
@@ -342,6 +346,34 @@ void run_big_or_small(dataset_at& dataset, args_t const& args, config_t config)
342
346
  run_type_punned<auto_index_gt<std::size_t, neighbor_id_at>>(dataset, args, config);
343
347
  }
344
348
 
349
+ void report_expected_losses(persisted_dataset_gt<float, vector_id_t> const& dataset) {
350
+
351
+ auto vec1 = dataset.vector(0);
352
+ auto vec2 = dataset.query(0);
353
+ std::vector<f16_converted_t> vec1f16(dataset.dimensions());
354
+ std::vector<f16_converted_t> vec2f16(dataset.dimensions());
355
+ std::transform(vec1, vec1 + dataset.dimensions(), vec1f16.data(), [](float v) { return v; });
356
+ std::transform(vec2, vec2 + dataset.dimensions(), vec2f16.data(), [](float v) { return v; });
357
+ std::vector<i8q100_converted_t> vec1i8(dataset.dimensions());
358
+ std::vector<i8q100_converted_t> vec2i8(dataset.dimensions());
359
+ std::transform(vec1, vec1 + dataset.dimensions(), vec1i8.data(), [](float v) { return v; });
360
+ std::transform(vec2, vec2 + dataset.dimensions(), vec2i8.data(), [](float v) { return v; });
361
+
362
+ #if 0
363
+ auto ip_default = ip_gt<float>{}(vec1, vec2, dataset.dimensions());
364
+ auto ip_sve = 1.f - simsimd_dot_f32sve(vec1, vec2, dataset.dimensions());
365
+ auto ip_neon = 1.f - simsimd_dot_f32x4neon(vec1, vec2, dataset.dimensions());
366
+ auto ip_f16 = ip_gt<f16_converted_t>{}(vec1f16.data(), vec2f16.data(), dataset.dimensions());
367
+ auto ip_f16sve = 1.f - simsimd_dot_f16sve((simsimd_f16_t const*)vec1f16.data(),
368
+ (simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
369
+ auto ip_f16neon = 1.f - simsimd_dot_f16x8neon((simsimd_f16_t const*)vec1f16.data(),
370
+ (simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
371
+ auto ip_i8 = ip_i8q100_converted_t{}(vec1i8.data(), vec2i8.data(), dataset.dimensions());
372
+
373
+ exit(0);
374
+ #endif
375
+ }
376
+
345
377
  int main(int argc, char** argv) {
346
378
 
347
379
  // Print backtrace if something goes wrong.
@@ -391,8 +423,6 @@ int main(int argc, char** argv) {
391
423
  std::printf("-- Query vectors path: %s\n", args.path_queries.c_str());
392
424
  std::printf("-- Ground truth neighbors path: %s\n", args.path_neighbors.c_str());
393
425
 
394
- using vector_id_t = std::uint32_t;
395
-
396
426
  persisted_dataset_gt<float, vector_id_t> dataset{
397
427
  args.path_vectors.c_str(),
398
428
  args.path_queries.c_str(),
@@ -410,6 +440,11 @@ int main(int argc, char** argv) {
410
440
  config.max_threads_add = config.max_threads_search = args.threads;
411
441
  config.max_elements = dataset.vectors_count();
412
442
 
443
+ std::printf("- Index: \n");
444
+ std::printf("-- Connectivity: %zu\n", config.connectivity);
445
+ std::printf("-- Expansion @ Add: %zu\n", config.expansion_add);
446
+ std::printf("-- Expansion @ Search: %zu\n", config.expansion_search);
447
+
413
448
  if (args.big)
414
449
  run_big_or_small<uint40_t>(dataset, args, config);
415
450
  else
@@ -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")