usearch 0.2.0 → 0.2.3

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/src/bench.cpp CHANGED
@@ -3,12 +3,26 @@
3
3
  * and the resulting accuracy (recall) of the Approximate Nearest Neighbors
4
4
  * Search queries.
5
5
  */
6
+
7
+ #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
8
+ #define NOMINMAX // define this macro to prevent the definition of min/max macros in Windows.h
9
+ #define _USE_MATH_DEFINES
10
+
11
+ #include <Windows.h>
12
+
13
+ #include <DbgHelp.h>
14
+ #pragma comment(lib, "Dbghelp.lib")
15
+
16
+ #define STDERR_FILENO HANDLE(2)
17
+ #else
6
18
  #include <execinfo.h>
7
19
  #include <fcntl.h> // `open`
8
20
  #include <stdlib.h> // `getenv`
9
21
  #include <sys/mman.h> // `mmap`
10
- #include <sys/stat.h> // `stat`
11
22
  #include <unistd.h>
23
+ #endif
24
+
25
+ #include <sys/stat.h> // `stat`
12
26
 
13
27
  #include <csignal>
14
28
  #include <cstdio>
@@ -19,7 +33,9 @@
19
33
  #include <variant> // `std::monostate`
20
34
 
21
35
  #include <clipp.h> // Command Line Interface
22
- #include <omp.h> // `omp_get_num_threads()`
36
+ #include <omp.h> // `omp_set_num_threads()`
37
+
38
+ #include <simsimd/simsimd.h>
23
39
 
24
40
  #include "advanced.hpp"
25
41
 
@@ -53,6 +69,34 @@ struct alignas(32) persisted_matrix_gt {
53
69
  persisted_matrix_gt(char const* path) noexcept(false) {
54
70
  if (!path || !std::strlen(path))
55
71
  throw std::invalid_argument("The file path is empty");
72
+ #if defined(WINDOWS)
73
+
74
+ HANDLE file_handle =
75
+ CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
76
+
77
+ if (file_handle == INVALID_HANDLE_VALUE)
78
+ throw std::invalid_argument("Couldn't open provided file path");
79
+
80
+ LARGE_INTEGER file_size;
81
+ if (!GetFileSizeEx(file_handle, &file_size))
82
+ throw std::invalid_argument("Couldn't obtain file stats");
83
+
84
+ raw_length = file_size.QuadPart;
85
+ HANDLE mapping_handle = CreateFileMapping(file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr);
86
+
87
+ if (mapping_handle == nullptr)
88
+ throw std::invalid_argument("Couldn't create file mapping");
89
+
90
+ raw_handle = (std::uint8_t*)MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, raw_length);
91
+
92
+ if (raw_handle == nullptr)
93
+ throw std::invalid_argument("Couldn't memory-map the file");
94
+
95
+ std::memcpy(&rows, raw_handle, sizeof(rows));
96
+ std::memcpy(&cols, raw_handle + sizeof(rows), sizeof(cols));
97
+ scalars = (scalar_t*)(raw_handle + sizeof(rows) + sizeof(cols));
98
+
99
+ #else
56
100
  auto file_descriptor = open(path, O_RDONLY | O_CLOEXEC);
57
101
  if (file_descriptor == -1)
58
102
  throw std::invalid_argument("Couldn't open provided file path");
@@ -66,11 +110,16 @@ struct alignas(32) persisted_matrix_gt {
66
110
  std::memcpy(&rows, raw_handle, sizeof(rows));
67
111
  std::memcpy(&cols, raw_handle + sizeof(rows), sizeof(cols));
68
112
  scalars = (scalar_t*)(raw_handle + sizeof(rows) + sizeof(cols));
113
+ #endif // WINDOWS
69
114
  }
70
115
 
71
116
  ~persisted_matrix_gt() {
72
117
  if (raw_handle != nullptr)
118
+ #if defined(WINDOWS)
119
+ UnmapViewOfFile(raw_handle);
120
+ #else
73
121
  munmap((void*)raw_handle, raw_length);
122
+ #endif // WINDOWS
74
123
  }
75
124
 
76
125
  scalar_t const* row(std::size_t i) const noexcept { return scalars + i * cols; }
@@ -97,9 +146,14 @@ struct persisted_dataset_gt {
97
146
  persisted_matrix_gt<scalar_t> vectors_;
98
147
  persisted_matrix_gt<scalar_t> queries_;
99
148
  persisted_matrix_gt<vector_id_t> neighborhoods_;
149
+ std::size_t vectors_to_skip_{};
150
+ std::size_t vectors_to_take_{};
151
+
152
+ persisted_dataset_gt(char const* path_vectors, char const* path_queries, char const* path_neighbors,
153
+ std::size_t vectors_to_skip = 0, std::size_t vectors_to_take = 0) noexcept(false)
154
+ : vectors_(path_vectors), queries_(path_queries), neighborhoods_(path_neighbors),
155
+ vectors_to_skip_(vectors_to_skip), vectors_to_take_(vectors_to_take) {
100
156
 
101
- persisted_dataset_gt(char const* path_vectors, char const* path_queries, char const* path_neighbors) noexcept(false)
102
- : vectors_(path_vectors), queries_(path_queries), neighborhoods_(path_neighbors) {
103
157
  if (vectors_.cols != queries_.cols)
104
158
  throw std::invalid_argument("Contents and queries have different dimensionality");
105
159
  if (queries_.rows != neighborhoods_.rows)
@@ -107,14 +161,18 @@ struct persisted_dataset_gt {
107
161
  }
108
162
 
109
163
  std::size_t dimensions() const noexcept { return vectors_.cols; }
110
- std::size_t vectors_count() const noexcept { return vectors_.rows; }
111
164
  std::size_t queries_count() const noexcept { return queries_.rows; }
112
165
  std::size_t neighborhood_size() const noexcept { return neighborhoods_.cols; }
113
- scalar_t const* vector(std::size_t i) const noexcept { return vectors_.row(i); }
166
+ scalar_t const* vector(std::size_t i) const noexcept { return vectors_.row(i + vectors_to_skip_); }
114
167
  scalar_t const* query(std::size_t i) const noexcept { return queries_.row(i); }
115
168
  vector_id_t const* neighborhood(std::size_t i) const noexcept { return neighborhoods_.row(i); }
116
169
 
117
- vectors_view_gt<scalar_t> vectors_view() const noexcept { return {vector(0), vectors_count(), dimensions()}; }
170
+ std::size_t vectors_count() const noexcept {
171
+ return vectors_to_take_ ? vectors_to_take_ : (vectors_.rows - vectors_to_skip_);
172
+ }
173
+ vectors_view_gt<scalar_t> vectors_view() const noexcept {
174
+ return {vector(vectors_to_skip_), vectors_count(), dimensions()};
175
+ }
118
176
  };
119
177
 
120
178
  template <typename scalar_at, typename vector_id_at> //
@@ -156,16 +214,72 @@ struct in_memory_dataset_gt {
156
214
 
157
215
  char const* getenv_or(char const* name, char const* default_) { return getenv(name) ? getenv(name) : default_; }
158
216
 
217
+ using timestamp_t = std::chrono::time_point<std::chrono::high_resolution_clock>;
218
+
219
+ struct running_stats_printer_t {
220
+ std::size_t total{};
221
+ std::atomic<std::size_t> progress{};
222
+ std::size_t last_printed_progress{};
223
+ timestamp_t last_printed_time{};
224
+ timestamp_t start_time{};
225
+
226
+ running_stats_printer_t(std::size_t n, char const* msg) {
227
+ std::printf("%s. %zu items\n", msg, n);
228
+ total = n;
229
+ start_time = std::chrono::high_resolution_clock::now();
230
+ }
231
+
232
+ ~running_stats_printer_t() {
233
+ std::size_t count = progress.load();
234
+ timestamp_t time = std::chrono::high_resolution_clock::now();
235
+ std::size_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(time - start_time).count();
236
+ float vectors_per_second = count * 1e9 / duration;
237
+ std::printf("\r\33[2K100 %% completed, %.0f vectors/s\n", vectors_per_second);
238
+ }
239
+
240
+ void refresh(std::size_t step = 1024 * 32) {
241
+ std::size_t new_progress = progress.load();
242
+ if (new_progress - last_printed_progress < step)
243
+ return;
244
+ print(new_progress);
245
+ }
246
+
247
+ void print(std::size_t progress) {
248
+
249
+ constexpr char bars_k[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||";
250
+ constexpr std::size_t bars_len_k = 60;
251
+
252
+ float percentage = progress * 1.f / total;
253
+ int lpad = (int)(percentage * bars_len_k);
254
+ int rpad = bars_len_k - lpad;
255
+
256
+ std::size_t count_new = progress - last_printed_progress;
257
+ timestamp_t time_new = std::chrono::high_resolution_clock::now();
258
+ std::size_t duration =
259
+ std::chrono::duration_cast<std::chrono::nanoseconds>(time_new - last_printed_time).count();
260
+ float vectors_per_second = count_new * 1e9 / duration;
261
+
262
+ std::printf("\r%3.3f%% [%.*s%*s] %.0f vectors/s, finished %zu/%zu", percentage * 100.f, lpad, bars_k, rpad, "",
263
+ vectors_per_second, progress, total);
264
+ std::fflush(stdout);
265
+
266
+ last_printed_progress = progress;
267
+ last_printed_time = time_new;
268
+ }
269
+ };
270
+
159
271
  template <typename index_at, typename vector_id_at, typename real_at>
160
272
  void index_many(index_at& native, std::size_t n, vector_id_at const* ids, real_at const* vectors, std::size_t dims) {
161
273
 
162
274
  using span_t = span_gt<float const>;
275
+ running_stats_printer_t printer{n, "Indexing"};
163
276
 
164
- #pragma omp parallel for
277
+ #pragma omp parallel for schedule(static, 32)
165
278
  for (std::size_t i = 0; i < n; ++i) {
166
- native.add(ids[i], span_t{vectors + dims * i, dims}, omp_get_thread_num(), false);
167
- if (((i + 1) % 100000) == 0)
168
- std::printf("-- added point # %zu\n", i + 1);
279
+ native.add(ids[i], span_t{vectors + dims * i, dims}, omp_get_thread_num(), true);
280
+ printer.progress++;
281
+ if (omp_get_thread_num() == 0)
282
+ printer.refresh();
169
283
  }
170
284
  }
171
285
 
@@ -174,52 +288,39 @@ void search_many(index_at& native, std::size_t n, real_at const* vectors, std::s
174
288
  vector_id_at* ids, real_at* distances) {
175
289
 
176
290
  using span_t = span_gt<float const>;
291
+ running_stats_printer_t printer{n, "Search"};
177
292
 
178
- #pragma omp parallel for
293
+ #pragma omp parallel for schedule(static, 32)
179
294
  for (std::size_t i = 0; i < n; ++i) {
180
- std::size_t found = native.search( //
295
+ native.search( //
181
296
  span_t{vectors + dims * i, dims}, wanted, //
182
297
  ids + wanted * i, distances + wanted * i, //
183
298
  omp_get_thread_num());
299
+ printer.progress++;
300
+ if (omp_get_thread_num() == 0)
301
+ printer.refresh();
184
302
  }
185
303
  }
186
304
 
187
- template <typename callback_at> std::size_t nanoseconds(callback_at&& callback) {
188
- auto t_start = std::chrono::high_resolution_clock::now();
189
- callback();
190
- auto t_end = std::chrono::high_resolution_clock::now();
191
- auto dt_add = std::chrono::duration_cast<std::chrono::nanoseconds>(t_end - t_start).count();
192
- return dt_add;
193
- }
194
-
195
305
  template <typename dataset_at, typename index_at> //
196
306
  static void single_shot(dataset_at& dataset, index_at& index, bool construct = true) {
197
307
  using label_t = typename index_at::label_t;
198
308
  using distance_t = typename index_at::distance_t;
199
309
 
310
+ std::printf("\n");
311
+ std::printf("------------\n");
200
312
  if (construct) {
201
313
  // Perform insertions, evaluate speed
202
314
  std::vector<label_t> ids(dataset.vectors_count());
203
315
  std::iota(ids.begin(), ids.end(), 0);
204
- auto dt_add = nanoseconds(
205
- [&] { index_many(index, dataset.vectors_count(), ids.data(), dataset.vector(0), dataset.dimensions()); });
206
- std::printf("- Added %.2f vectors/sec\n", dataset.vectors_count() * 1e9 / dt_add);
316
+ index_many(index, dataset.vectors_count(), ids.data(), dataset.vector(0), dataset.dimensions());
207
317
  }
208
318
 
209
319
  // Perform search, evaluate speed
210
320
  std::vector<label_t> found_neighbors(dataset.queries_count() * dataset.neighborhood_size());
211
321
  std::vector<distance_t> found_distances(dataset.queries_count() * dataset.neighborhood_size());
212
- std::size_t executed_search_queries = 0;
213
-
214
- auto dt_search = nanoseconds([&] {
215
- while (executed_search_queries < dataset.vectors_count()) {
216
- search_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(),
217
- dataset.neighborhood_size(), found_neighbors.data(), found_distances.data());
218
- executed_search_queries += dataset.queries_count();
219
- std::printf("- Searched %zu vectors\n", executed_search_queries);
220
- }
221
- });
222
- std::printf("- Searched %.2f vectors/sec\n", executed_search_queries * 1e9 / dt_search);
322
+ search_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(), dataset.neighborhood_size(),
323
+ found_neighbors.data(), found_distances.data());
223
324
 
224
325
  // Evaluate quality
225
326
  std::size_t recall_at_1 = 0, recall_full = 0;
@@ -229,8 +330,11 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
229
330
  recall_at_1 += expected[0] == received[0];
230
331
  recall_full += contains(received, received + dataset.neighborhood_size(), label_t{expected[0]});
231
332
  }
232
- std::printf("- Recall@1 %.2f %%\n", recall_at_1 * 100.f / dataset.queries_count());
233
- std::printf("- Recall %.2f %%\n", recall_full * 100.f / dataset.queries_count());
333
+
334
+ std::printf("Recall@1 %.2f %%\n", recall_at_1 * 100.f / dataset.queries_count());
335
+ std::printf("Recall %.2f %%\n", recall_full * 100.f / dataset.queries_count());
336
+ std::printf("------------\n");
337
+ std::printf("\n");
234
338
  }
235
339
 
236
340
  void handler(int sig) {
@@ -238,11 +342,34 @@ void handler(int sig) {
238
342
  size_t size;
239
343
 
240
344
  // get void*'s for all entries on the stack
345
+ #if defined(WINDOWS)
346
+ size = CaptureStackBackTrace(0, 10, array, NULL);
347
+ #else
241
348
  size = backtrace(array, 10);
349
+ #endif // WINDOWS
242
350
 
243
351
  // print out all the frames to stderr
244
352
  fprintf(stderr, "Error: signal %d:\n", sig);
353
+
354
+ #if defined(WINDOWS)
355
+ SYMBOL_INFO* symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
356
+ symbol->MaxNameLen = 255;
357
+ symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
358
+ for (int i = 0; i < size; i++) {
359
+ SymFromAddr(GetCurrentProcess(), (DWORD64)(array[i]), 0, symbol);
360
+ const char* name = symbol->Name;
361
+ if (name == NULL) {
362
+ name = "<unknown>";
363
+ }
364
+ DWORD bytes_written;
365
+ WriteFile(STDERR_FILENO, name, strlen(name), &bytes_written, NULL);
366
+ WriteFile(STDERR_FILENO, "\n", 1, &bytes_written, NULL);
367
+ }
368
+ free(symbol);
369
+ #else
245
370
  backtrace_symbols_fd(array, size, STDERR_FILENO);
371
+ #endif // WINDOWS
372
+
246
373
  exit(1);
247
374
  }
248
375
 
@@ -256,18 +383,22 @@ struct args_t {
256
383
  std::string path_vectors;
257
384
  std::string path_queries;
258
385
  std::string path_neighbors;
386
+ std::string path_output = "last.usearch";
259
387
 
260
388
  std::size_t connectivity = config_t::connectivity_default_k;
261
389
  std::size_t expansion_add = config_t::expansion_add_default_k;
262
390
  std::size_t expansion_search = config_t::expansion_search_default_k;
263
391
  std::size_t threads = std::thread::hardware_concurrency();
264
392
 
393
+ std::size_t vectors_to_skip = 0;
394
+ std::size_t vectors_to_take = 0;
395
+
265
396
  bool help = false;
266
397
 
267
398
  bool big = false;
268
399
  bool native = false;
269
400
  bool quantize_f16 = false;
270
- bool quantize_i8 = false;
401
+ bool quantize_f8 = false;
271
402
 
272
403
  bool metric_ip = true;
273
404
  bool metric_l2 = false;
@@ -277,15 +408,23 @@ struct args_t {
277
408
 
278
409
  template <typename index_at, typename dataset_at> //
279
410
  index_at type_punned_index_for_metric(dataset_at& dataset, args_t const& args, config_t config, accuracy_t accuracy) {
280
- if (args.metric_ip)
411
+ if (args.metric_ip) {
412
+ std::printf("-- Metric: Inner Product\n");
281
413
  return index_at::ip(dataset.dimensions(), accuracy, config);
282
- if (args.metric_l2)
414
+ }
415
+ if (args.metric_l2) {
416
+ std::printf("-- Metric: Euclidean\n");
283
417
  return index_at::l2(dataset.dimensions(), accuracy, config);
284
- if (args.metric_cos)
418
+ }
419
+ if (args.metric_cos) {
420
+ std::printf("-- Metric: Angular\n");
285
421
  return index_at::cos(dataset.dimensions(), accuracy, config);
286
- if (args.metric_haversine)
422
+ }
423
+ if (args.metric_haversine) {
424
+ std::printf("-- Metric: Haversine\n");
287
425
  return index_at::haversine(accuracy, config);
288
- return index_at{};
426
+ }
427
+ throw std::invalid_argument("Unknown metric!");
289
428
  }
290
429
 
291
430
  template <typename index_at, typename dataset_at> //
@@ -294,54 +433,57 @@ void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
294
433
  accuracy_t accuracy = accuracy_t::f32_k;
295
434
  if (args.quantize_f16)
296
435
  accuracy = accuracy_t::f16_k;
297
- if (args.quantize_i8)
298
- accuracy = accuracy_t::i8q100_k;
436
+ if (args.quantize_f8)
437
+ accuracy = accuracy_t::f8_k;
299
438
 
300
439
  std::printf("-- Accuracy: %s\n", accuracy_name(accuracy));
301
440
 
302
441
  index_at index{type_punned_index_for_metric<index_at>(dataset, args, config, accuracy)};
303
- std::printf("- Hardware acceleration: %s\n", isa_name(index.acceleration()));
304
- std::printf("-- Will benchmark in-memory\n");
442
+ std::printf("-- Hardware acceleration: %s\n", isa_name(index.acceleration()));
443
+ std::printf("Will benchmark in-memory\n");
305
444
 
306
445
  single_shot(dataset, index, true);
307
- index.save("tmp.usearch");
446
+ index.save(args.path_output.c_str());
308
447
 
309
- std::printf("-------\n");
310
- std::printf("-- Will benchmark an on-disk view\n");
448
+ std::printf("Will benchmark an on-disk view\n");
311
449
 
312
450
  index_at index_view = index.fork();
313
- index_view.view("tmp.usearch");
451
+ index_view.view(args.path_output.c_str());
314
452
  single_shot(dataset, index_view, false);
315
453
  }
316
454
 
317
455
  template <typename index_at, typename dataset_at> //
318
- void run_typed(dataset_at& dataset, config_t config) {
456
+ void run_typed(dataset_at& dataset, args_t const& args, config_t config) {
319
457
 
320
458
  index_at index(config);
321
- std::printf("-- Will benchmark in-memory\n");
459
+ std::printf("Will benchmark in-memory\n");
322
460
 
323
461
  single_shot(dataset, index, true);
324
- index.save("tmp.usearch");
462
+ index.save(args.path_output.c_str());
325
463
 
326
- std::printf("-------\n");
327
- std::printf("-- Will benchmark an on-disk view\n");
464
+ std::printf("Will benchmark an on-disk view\n");
328
465
 
329
- auto_index_t index_view;
330
- index_view.view("tmp.usearch");
466
+ index_at index_view = index.fork();
467
+ index_view.view(args.path_output.c_str());
331
468
  single_shot(dataset, index_view, false);
332
469
  }
333
470
 
334
471
  template <typename neighbor_id_at, typename dataset_at> //
335
472
  void run_big_or_small(dataset_at& dataset, args_t const& args, config_t config) {
336
473
  if (args.native) {
337
- if (args.metric_cos)
338
- run_typed<index_gt<ip_gt<float>, std::size_t, neighbor_id_at>>(dataset, config);
339
- else if (args.metric_l2)
340
- run_typed<index_gt<l2_squared_gt<float>, std::size_t, neighbor_id_at>>(dataset, config);
341
- else if (args.metric_haversine)
342
- run_typed<index_gt<haversine_gt<float>, std::size_t, neighbor_id_at>>(dataset, config);
343
- else
344
- run_typed<index_gt<ip_gt<float>, std::size_t, neighbor_id_at>>(dataset, config);
474
+ if (args.metric_cos) {
475
+ std::printf("-- Metric: Inner Product\n");
476
+ run_typed<index_gt<ip_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
477
+ } else if (args.metric_l2) {
478
+ std::printf("-- Metric: Euclidean\n");
479
+ run_typed<index_gt<l2sq_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
480
+ } else if (args.metric_haversine) {
481
+ std::printf("-- Metric: Angular\n");
482
+ run_typed<index_gt<haversine_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
483
+ } else {
484
+ std::printf("-- Metric: Haversine\n");
485
+ run_typed<index_gt<ip_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
486
+ }
345
487
  } else
346
488
  run_type_punned<auto_index_gt<std::size_t, neighbor_id_at>>(dataset, args, config);
347
489
  }
@@ -362,26 +504,34 @@ void report_expected_losses(persisted_dataset_gt<float, vector_id_t> const& data
362
504
 
363
505
  auto vec1 = dataset.vector(0);
364
506
  auto vec2 = dataset.query(0);
365
- std::vector<f16_converted_t> vec1f16(dataset.dimensions());
366
- std::vector<f16_converted_t> vec2f16(dataset.dimensions());
507
+ std::vector<f16_bits_t> vec1f16(dataset.dimensions());
508
+ std::vector<f16_bits_t> vec2f16(dataset.dimensions());
367
509
  std::transform(vec1, vec1 + dataset.dimensions(), vec1f16.data(), [](float v) { return v; });
368
510
  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; });
511
+ std::vector<f8_bits_t> vec1f8(dataset.dimensions());
512
+ std::vector<f8_bits_t> vec2f8(dataset.dimensions());
513
+ std::transform(vec1, vec1 + dataset.dimensions(), vec1f8.data(), [](float v) { return v; });
514
+ std::transform(vec2, vec2 + dataset.dimensions(), vec2f8.data(), [](float v) { return v; });
373
515
 
374
516
  #if 0
375
517
  auto ip_default = ip_gt<float>{}(vec1, vec2, dataset.dimensions());
518
+ auto ip_f16 = ip_gt<f16_bits_t, float>{}(vec1f16.data(), vec2f16.data(), dataset.dimensions());
519
+ auto ip_f8 = ip_gt<f8_bits_t, float>{}(vec1f8.data(), vec2f8.data(), dataset.dimensions());
520
+
521
+ #if defined(__AVX512F__)
522
+ auto ip_f16avx512 = 1.f - simsimd_dot_f16x16avx512((simsimd_f16_t const*)vec1f16.data(),
523
+ (simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
524
+ #endif
525
+ #if defined(__ARM_FEATURE_SVE)
376
526
  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
527
  auto ip_f16sve = 1.f - simsimd_dot_f16sve((simsimd_f16_t const*)vec1f16.data(),
380
528
  (simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
529
+ #endif
530
+ #if defined(__ARM_NEON)
531
+ auto ip_neon = 1.f - simsimd_dot_f32x4neon(vec1, vec2, dataset.dimensions());
381
532
  auto ip_f16neon = 1.f - simsimd_dot_f16x8neon((simsimd_f16_t const*)vec1f16.data(),
382
533
  (simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
383
- auto ip_i8 = ip_i8q100_converted_t{}(vec1i8.data(), vec2i8.data(), dataset.dimensions());
384
-
534
+ #endif
385
535
  exit(0);
386
536
  #endif
387
537
  }
@@ -398,15 +548,18 @@ int main(int argc, char** argv) {
398
548
  (option("--vectors") & value("path", args.path_vectors)).doc(".fbin file path to construct the index"),
399
549
  (option("--queries") & value("path", args.path_queries)).doc(".fbin file path to query the index"),
400
550
  (option("--neighbors") & value("path", args.path_neighbors)).doc(".ibin file path with ground truth"),
551
+ (option("-o", "--output") & value("path", args.path_output)).doc(".usearch output file path"),
401
552
  (option("-b", "--big").set(args.big)).doc("Will switch to uint40_t for neighbors lists with over 4B entries"),
402
553
  (option("-j", "--threads") & value("integer", args.threads)).doc("Uses all available cores by default"),
403
554
  (option("-c", "--connectivity") & value("integer", args.connectivity)).doc("Index granularity"),
404
555
  (option("--expansion-add") & value("integer", args.expansion_add)).doc("Affects indexing depth"),
405
556
  (option("--expansion-search") & value("integer", args.expansion_search)).doc("Affects search depth"),
557
+ (option("--rows-skip") & value("integer", args.vectors_to_skip)).doc("Number of vectors to skip"),
558
+ (option("--rows-take") & value("integer", args.vectors_to_take)).doc("Number of vectors to take"),
406
559
  ( //
407
560
  option("--native").set(args.native).doc("Use raw templates instead of type-punned classes") |
408
561
  option("--f16quant").set(args.quantize_f16).doc("Enable `f16_t` quantization") |
409
- option("--i8quant").set(args.quantize_i8).doc("Enable `int8_t` quantization")),
562
+ option("--f8quant").set(args.quantize_f8).doc("Enable `f8_t` quantization")),
410
563
  ( //
411
564
  option("--ip").set(args.metric_ip).doc("Choose Inner Product metric") |
412
565
  option("--l2").set(args.metric_l2).doc("Choose L2 Euclidean metric") |
@@ -436,17 +589,19 @@ int main(int argc, char** argv) {
436
589
  std::printf("-- Ground truth neighbors path: %s\n", args.path_neighbors.c_str());
437
590
 
438
591
  persisted_dataset_gt<float, vector_id_t> dataset{
439
- args.path_vectors.c_str(),
440
- args.path_queries.c_str(),
441
- args.path_neighbors.c_str(),
592
+ args.path_vectors.c_str(), //
593
+ args.path_queries.c_str(), //
594
+ args.path_neighbors.c_str(), //
595
+ args.vectors_to_skip, //
596
+ args.vectors_to_take, //
442
597
  };
443
598
  std::printf("-- Dimensions: %zu\n", dataset.dimensions());
444
599
  std::printf("-- Vectors count: %zu\n", dataset.vectors_count());
445
600
  std::printf("-- Queries count: %zu\n", dataset.queries_count());
446
601
  std::printf("-- Neighbors per query: %zu\n", dataset.neighborhood_size());
447
602
 
448
- report_alternative_setups();
449
- report_expected_losses(dataset);
603
+ // report_alternative_setups();
604
+ // report_expected_losses(dataset);
450
605
 
451
606
  config_t config;
452
607
  config.connectivity = args.connectivity;
@@ -0,0 +1,70 @@
1
+ //
2
+ // Index+Sugar.swift
3
+ //
4
+ //
5
+ // Created by Ashot Vardanian on 5/11/23.
6
+ //
7
+
8
+ extension Index {
9
+
10
+ public func add(label: UInt32, vector: ArraySlice<Float32>) -> () {
11
+ vector.withContiguousStorageIfAvailable {
12
+ addSingle(label: label, vector: $0.baseAddress!)
13
+ }
14
+ }
15
+
16
+
17
+ public func search(vector: ArraySlice<Float32>, count: Int) -> ([UInt32], [Float]) {
18
+ var matches: [UInt32] = Array(repeating: 0, count: count)
19
+ var distances: [Float] = Array(repeating: 0, count: count)
20
+ let results = vector.withContiguousStorageIfAvailable {
21
+ searchSingle(vector: $0.baseAddress!, count: CUnsignedInt(count), labels: &matches, distances: &distances)
22
+ }
23
+ matches.removeLast(count - Int(results!))
24
+ distances.removeLast(count - Int(results!))
25
+ return (matches, distances)
26
+ }
27
+
28
+ public func add(label: UInt32, vector: ArraySlice<Float64>) -> () {
29
+ vector.withContiguousStorageIfAvailable {
30
+ addPrecise(label: label, vector: $0.baseAddress!)
31
+ }
32
+ }
33
+
34
+ public func search(vector: ArraySlice<Float64>, count: Int) -> ([UInt32], [Float]) {
35
+ var matches: [UInt32] = Array(repeating: 0, count: count)
36
+ var distances: [Float] = Array(repeating: 0, count: count)
37
+ let results = vector.withContiguousStorageIfAvailable {
38
+ searchPrecise(vector: $0.baseAddress!, count: CUnsignedInt(count), labels: &matches, distances: &distances)
39
+ }
40
+ matches.removeLast(count - Int(results!))
41
+ distances.removeLast(count - Int(results!))
42
+ return (matches, distances)
43
+ }
44
+
45
+ #if arch(arm64)
46
+
47
+ @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
48
+ public func add(label: UInt32, vector: ArraySlice<Float16>) -> () {
49
+ vector.withContiguousStorageIfAvailable { buffer in
50
+ addImprecise(label: label, vector: buffer.baseAddress!)
51
+ }
52
+ }
53
+
54
+ @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
55
+ public func search(vector: ArraySlice<Float16>, count: Int) -> ([UInt32], [Float]) {
56
+ var matches: [UInt32] = Array(repeating: 0, count: count)
57
+ var distances: [Float] = Array(repeating: 0, count: count)
58
+ let results = vector.withContiguousStorageIfAvailable {
59
+ searchImprecise(vector: $0.baseAddress!, count: CUnsignedInt(count), labels: &matches, distances: &distances)
60
+ }
61
+ matches.removeLast(count - Int(results!))
62
+ distances.removeLast(count - Int(results!))
63
+ return (matches, distances)
64
+ }
65
+
66
+ #endif
67
+
68
+ public var count: Int { get { return Int(length) } }
69
+
70
+ }
@@ -0,0 +1,20 @@
1
+ //
2
+ // File.swift
3
+ //
4
+ //
5
+ // Created by Ashot Vardanian on 5/11/23.
6
+ //
7
+
8
+ import Foundation
9
+ import USearch
10
+
11
+ func testUnit() throws {
12
+ let index = Index.l2(dimensions: 4, connectivity: 8)
13
+ let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
14
+ let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1]
15
+ index.add(label: 42, vector: vectorA[...])
16
+ index.add(label: 43, vector: vectorB[...])
17
+
18
+ let results = index.search(vector: vectorA[...], count: 10)
19
+ assert(results.0[0] == 42)
20
+ }
@@ -0,0 +1,8 @@
1
+ //
2
+ // File.swift
3
+ //
4
+ //
5
+ // Created by Ashot Vardanian on 5/11/23.
6
+ //
7
+
8
+ @_exported import USearchObjective
@@ -1,6 +0,0 @@
1
- distributionBase=GRADLE_USER_HOME
2
- distributionPath=wrapper/dists
3
- distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
4
- networkTimeout=10000
5
- zipStoreBase=GRADLE_USER_HOME
6
- zipStorePath=wrapper/dists