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.
- package/.github/workflows/release.yml +12 -1
- package/.vscode/launch.json +5 -4
- package/.vscode/settings.json +2 -1
- package/CMakeLists.txt +11 -6
- package/CMakeListsInstructions.md +39 -17
- package/Cargo.toml +1 -1
- package/Package.swift +40 -0
- package/README.md +24 -11
- package/VERSION +1 -1
- package/build.gradle +5 -1
- package/cmake/jemalloc.cmake +0 -2
- package/conanfile.py +18 -0
- package/include/usearch/usearch.hpp +335 -227
- package/javascript/lib.cpp +8 -2
- package/objc/USearchObjective.mm +150 -0
- package/objc/include/USearchObjective.h +69 -0
- package/package.json +1 -1
- package/pyproject.toml +18 -4
- package/python/lib.cpp +6 -6
- package/python/test.py +4 -5
- package/python/usearch/__init__.py +0 -0
- package/rust/lib.cpp +13 -2
- package/setup.py +8 -3
- package/simsimd/include/simsimd/simsimd.h +15 -0
- package/src/advanced.hpp +217 -117
- package/src/bench.cpp +236 -81
- package/swift/Index+Sugar.swift +70 -0
- package/swift/Test.swift +20 -0
- package/swift/USearch.swift +8 -0
- package/java/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/java/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/java/gradlew +0 -244
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>
|
|
@@ -21,6 +35,8 @@
|
|
|
21
35
|
#include <clipp.h> // Command Line Interface
|
|
22
36
|
#include <omp.h> // `omp_get_num_threads()`
|
|
23
37
|
|
|
38
|
+
#include <simsimd/simsimd.h>
|
|
39
|
+
|
|
24
40
|
#include "advanced.hpp"
|
|
25
41
|
|
|
26
42
|
using namespace unum::usearch;
|
|
@@ -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
|
-
|
|
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(),
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
std::printf("
|
|
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
|
|
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
|
-
|
|
414
|
+
}
|
|
415
|
+
if (args.metric_l2) {
|
|
416
|
+
std::printf("-- Metric: Euclidean\n");
|
|
283
417
|
return index_at::l2(dataset.dimensions(), accuracy, config);
|
|
284
|
-
|
|
418
|
+
}
|
|
419
|
+
if (args.metric_cos) {
|
|
420
|
+
std::printf("-- Metric: Angular\n");
|
|
285
421
|
return index_at::cos(dataset.dimensions(), accuracy, config);
|
|
286
|
-
|
|
422
|
+
}
|
|
423
|
+
if (args.metric_haversine) {
|
|
424
|
+
std::printf("-- Metric: Haversine\n");
|
|
287
425
|
return index_at::haversine(accuracy, config);
|
|
288
|
-
|
|
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.
|
|
298
|
-
accuracy = accuracy_t::
|
|
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("
|
|
304
|
-
std::printf("
|
|
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(
|
|
446
|
+
index.save(args.path_output.c_str());
|
|
308
447
|
|
|
309
|
-
std::printf("
|
|
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(
|
|
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("
|
|
459
|
+
std::printf("Will benchmark in-memory\n");
|
|
322
460
|
|
|
323
461
|
single_shot(dataset, index, true);
|
|
324
|
-
index.save(
|
|
462
|
+
index.save(args.path_output.c_str());
|
|
325
463
|
|
|
326
|
-
std::printf("
|
|
327
|
-
std::printf("-- Will benchmark an on-disk view\n");
|
|
464
|
+
std::printf("Will benchmark an on-disk view\n");
|
|
328
465
|
|
|
329
|
-
|
|
330
|
-
index_view.view(
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
run_typed<index_gt<
|
|
343
|
-
else
|
|
344
|
-
|
|
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<
|
|
366
|
-
std::vector<
|
|
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<
|
|
370
|
-
std::vector<
|
|
371
|
-
std::transform(vec1, vec1 + dataset.dimensions(),
|
|
372
|
-
std::transform(vec2, vec2 + dataset.dimensions(),
|
|
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
|
-
|
|
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("--
|
|
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
|
+
}
|
package/swift/Test.swift
ADDED
|
@@ -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
|
+
}
|
|
Binary file
|