usearch 0.1.8 → 0.1.9

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/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.1.8"
3
+ version = "0.1.9"
4
4
  authors = ["Ashot Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "The Mighty Tiny Vector Search Engine with Automatic Quantization and Hardware Acceleration"
6
6
  edition = "2021"
package/README.md CHANGED
@@ -17,11 +17,11 @@ Compact, yet Powerful<br/>
17
17
  <a href="https://github.com/unum-cloud/usearch"><img height="25" src="https://github.com/unum-cloud/.github/raw/main/assets/github.svg" alt="GitHub"></a>
18
18
  </p>
19
19
 
20
- - [x] Single C++11 header implementation, easily extendible.
21
- - [x] 4B+ sized space efficient point-clouds with `uint40_t`.
22
- - [x] Half-precision support with [`maratyszcza/fp16`](https://github.com/maratyszcza/fp16).
23
- - [x] View from disk, without loading into RAM.
24
- - [x] Any metric, includes:
20
+ - [x] [Single C++11 header][usearch-header] implementation, easily extendible.
21
+ - [x] [4B+](#go-beyond-4b-entries) sized space efficient point-clouds with `uint40_t`.
22
+ - [x] [Half-precision](#quantize-on-the-fly) support with [`maratyszcza/fp16`](https://github.com/maratyszcza/fp16).
23
+ - [x] [View from disk](#view-larger-indexes-from-disk), without loading into RAM.
24
+ - [x] [Any metric](#define-custom-metrics), includes:
25
25
  - [x] Euclidean, Dot-product, Cosine,
26
26
  - [x] Jaccard, Hamming, Haversine.
27
27
  - [x] Hardware-accelerated [`ashvardanian/simsimd`](https://github.com/ashvardanian/simsimd).
@@ -29,10 +29,10 @@ Compact, yet Powerful<br/>
29
29
  - [x] Don't copy vectors if not needed.
30
30
  - [x] Bring your threads.
31
31
  - [x] Multiple vectors per label.
32
- - [x] Python bindings: `pip install usearch`.
33
- - [x] JavaScript bindings: `npm install usearch`.
34
- - [x] Rust bindings: `cargo add usearch`.
35
- - [x] Java bindings: `cloud.unum:usearch` on GitHub.
32
+ - [x] [Python](#python) bindings: `pip install usearch`.
33
+ - [x] [JavaScript](#java) bindings: `npm install usearch`.
34
+ - [x] [Rust](#rust) bindings: `cargo add usearch`.
35
+ - [x] [Java](#java) bindings: `cloud.unum:usearch` on GitHub.
36
36
  - [ ] GoLang bindings.
37
37
  - [ ] Wolfram language bindings.
38
38
  - [x] For Linux: GCC, Clang.
@@ -41,6 +41,96 @@ Compact, yet Powerful<br/>
41
41
  - [ ] Multi-index lookups in Python.
42
42
  - [ ] Thread-safe `reserve`.
43
43
  - [ ] Distributed construction.
44
+ - [x] AI + Vector Search = Semantic Search.
45
+
46
+ [usearch-header]: https://github.com/unum-cloud/usearch/blob/main/include/usearch/usearch.hpp
47
+
48
+ ## Features
49
+
50
+ ### Define Custom Metrics
51
+
52
+ Most vector-search packages focus on just 2 metrics - "Inner Product distance" and "Euclidean distance".
53
+ The lack of dedicated "Cosine distance" can be justified with the simplicity of normalizing such vectors on the fly.
54
+ But that hardly exhausts the list of possible metrics.
55
+
56
+ ![USearch: Vector Search Approaches](assets/usearch-approaches-white.png)
57
+
58
+ Older approaches indexing high-dimensional spaces, like KD-Trees and Locality Sensitive Hashing are hardly extendible to vectors/objects of variable length.
59
+ Modern NSW-like approaches, however, only require two objects to be comparable.
60
+ This means, the index can be extended to support fuzzy search with Levenstein distance over strings.
61
+ USearch implements HNSW, and provides some uncommon-to-vector-search "similarity measures" and "distances" out of the box: Haversine, Jaccard, Hamming, etc.
62
+
63
+ ### Bring your Threads
64
+
65
+ Most AI, HPC, or Big Data packages use some form of a thread pool.
66
+ Instead of spawning additional threads within USearch, we focus on thread-safety of the `add` function.
67
+
68
+ ```cpp
69
+ #pragma omp parallel for
70
+ for (std::size_t i = 0; i < n; ++i)
71
+ native.add(label, span_t{vector, dims}, omp_get_thread_num());
72
+ ```
73
+
74
+ During initialization we allocate enough temporary memory for all the cores on the machine.
75
+ On call, the user can simply supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
76
+
77
+ ### Go Beyond 4B Entries
78
+
79
+ Constructing a vector index is a memory-intensive task.
80
+ Depending on your configuration parameters, part of the memory will be allocated towards copies of vectors, and another part to the Small World Graph, common to NSW-like algorithms.
81
+ Choosing the right data-structures and numeric types can have profound implications on both.
82
+
83
+ ---
84
+
85
+ Internally, some point "label" is mapped into an integer identifier for every stored vector.
86
+ Most implementations hard-code it to `uint32_t`, as people rarely store more than 4 Billion vectors.
87
+ But when they do, software becomes unusable.
88
+
89
+ Others hard-code it to `uint64_t`, which hardly space-efficient.
90
+ We added a tiny class - `uint40_t`, that should be enough for collections up to 1 Trillion vectors.
91
+
92
+ ### View Larger Indexes from Disk
93
+
94
+ To construct a large index one may use a beefy RAM-optimized machine, like the AWS `u-24tb1.metal` instances with 24 TB of RAM.
95
+ Those are, however, pricy, and would cost you over $200/hour.
96
+
97
+ You wouldn't want to have them running all the time.
98
+ So, if you are working on large datasets, but don't need the in-RAM throughput, you can simply view an existing dataset from disk, without ever loading it fully into memory.
99
+
100
+ Construct on large machines, deploy on the cheap ones.
101
+
102
+ ### Quantize on the Fly
103
+
104
+ Most modern CPU have at least partial support for `half`-precision `f16_t` arithmetic.
105
+ USearch supports automatic down-casting and up-casting between `f32_t`, `f16_t`, `f64_t`, and `i8q100_t` representations.
106
+
107
+ Making vectors smaller will help pack more of them in-memory, while also increasing performance on CPUs implementing native support for target type.
108
+
109
+ ## Performance
110
+
111
+ Below are the performance numbers for a benchmark running on the 64 cores of AWS `c7g.metal` "Graviton 3"-based instances.
112
+ We fix the default configuration in the top line and show the affects of various parameters by changing one parameter at a time.
113
+
114
+ | Vectors | Connectivity | EF @ A | EF @ S | **Add**, QPS | **Search**, QPS | **Recall @1** |
115
+ | :--------: | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
116
+ | `f32` x256 | 16 | 128 | 64 | 75'640 | 131'654 | 99.3% |
117
+ | | | | | | | |
118
+ | `f32` x256 | 12 | 128 | 64 | 81'747 | 149'728 | 99.0% |
119
+ | `f32` x256 | 32 | 128 | 64 | 64'368 | 104'050 | 99.4% |
120
+ | | | | | | | |
121
+ | `f32` x256 | 16 | 64 | 32 | 128'644 | 228'422 | 97.2% |
122
+ | `f32` x256 | 16 | 256 | 128 | 39'981 | 69'065 | 99.2% |
123
+ | | | | | | | |
124
+ | `f16` x256 | 16 | 64 | 32 | 128'644 | 228'422 | 97.2% |
125
+ | `f32` x256 | 16 | 256 | 128 | 39'981 | 69'065 | 99.2% |
126
+
127
+ The main columns are:
128
+
129
+ - Add: Number of insertion Queries Per Second.
130
+ - Search: Number search Queries Per Second.
131
+ - Recall @1: How often does approximate search yield the exact best match?
132
+
133
+ To read more and reproduce, jump to [benchmarking section](#benchmarking).
44
134
 
45
135
  ## Usage
46
136
 
@@ -91,13 +181,32 @@ index.view("index.usearch"); // Memory-mapping from disk
91
181
  ```
92
182
 
93
183
  The `add` is thread-safe for concurrent index construction.
184
+ For advanced users, more compile-time abstraction are available.
185
+
186
+ ```cpp
187
+ template <typename metric_at = ip_gt<float>, //
188
+ typename label_at = std::size_t, // `uint32_t`, `uuid_t`...
189
+ typename id_at = std::uint32_t, // `uint40_t`, `uint64_t`...
190
+ typename scalar_at = float, // `double`, `half`, `char`...
191
+ typename allocator_at = std::allocator<char>> //
192
+ class index_gt;
193
+ ```
194
+
195
+ One may also define a custom metric, such as Damerau–Levenshtein distance, to compute the similarity between variable length strings.
196
+ The only constraint is the function signature:
197
+
198
+ ```cpp
199
+ struct custom_metric_t {
200
+ T operator()(T const* a, T const* b, std::size_t a_length, std::size_t b_length) const;
201
+ };
202
+ ```
94
203
 
95
204
  ### Python
96
205
 
97
206
  Python bindings are implemented with [`pybind/pybind11`](https://github.com/pybind/pybind11).
98
207
  Assuming the presence of Global Interpreter Lock in Python, on large insertions we spawn threads in the C++ layer.
99
208
 
100
- ```sh
209
+ ```python
101
210
  $ pip install usearch
102
211
 
103
212
  import numpy as np
@@ -125,13 +234,184 @@ assert len(index) == n
125
234
  matches, distances, counts = index.search(vectors, 10)
126
235
  ```
127
236
 
128
- ## Features
237
+ ### JavaScript
129
238
 
130
- ### Bring your Threads
239
+ ```js
240
+ // npm install usearch
131
241
 
132
- ## Performance
242
+ var index = new usearch.Index({ metric: 'cos', connectivity: 16, dimensions: 2 })
243
+ assert.equal(index.connectivity(), 16)
244
+ assert.equal(index.dimensions(), 2)
245
+ assert.equal(index.size(), 0)
246
+
247
+ index.add(15, new Float32Array([10, 20]))
248
+ assert.equal(index.size(), 2)
249
+
250
+ var results = index.search(new Float32Array([13, 14]), 2)
251
+ assert.deepEqual(results.labels, new Uint32Array([15, 16]))
252
+ assert.deepEqual(results.distances, new Float32Array([45, 130]))
253
+ ```
254
+
255
+ ### Rust
256
+
257
+ Being a systems-programming language, Rust has better control over memory management and concurrency, but lacks function overloading.
258
+ Aside from the `add` and `search`, it also provides `add_in_thread` and `search_in_thread` which let users identify the calling thread to use underlying temporary memory more efficiently.
259
+
260
+ ```rust
261
+ // cargo add usearch
262
+
263
+ let quant: &str = "f16";
264
+ let index = new_ip(5, &quant, 0, 0, 0).unwrap();
265
+
266
+ assert!(index.reserve(10).is_ok());
267
+ assert!(index.capacity() >= 10);
268
+ assert!(index.connectivity() != 0);
269
+ assert_eq!(index.dimensions(), 5);
270
+ assert_eq!(index.size(), 0);
271
+
272
+ let first: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
273
+ let second: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
274
+
275
+ assert!(index.add(42, &first).is_ok());
276
+ assert!(index.add(43, &second).is_ok());
277
+ assert_eq!(index.size(), 2);
278
+
279
+ // Read back the tags
280
+ let results = index.search(&first, 10).unwrap();
281
+ assert_eq!(results.count, 2);
282
+
283
+ // Validate serialization
284
+ assert!(index.save("index.usearch").is_ok());
285
+ assert!(index.load("index.usearch").is_ok());
286
+ assert!(index.view("index.usearch").is_ok());
287
+
288
+ // There are more "metrics" available
289
+ assert!(new_l2(5, &quant, 0, 0, 0).is_ok());
290
+ assert!(new_cos(5, &quant, 0, 0, 0).is_ok());
291
+ assert!(new_haversine(&quant, 0, 0, 0).is_ok());
292
+ ```
293
+
294
+ ### Java
295
+
296
+ ```java
297
+ Index index = new Index.Config().metric("cos").dimensions(2).build();
298
+ float vec[] = {10, 20};
299
+ index.add(42, vec);
300
+ int[] labels = index.search(vec, 5);
301
+ ```
302
+
303
+ ### GoLang
304
+
305
+ ### Wolfram
306
+
307
+ ## Benchmarking
308
+
309
+ All major HNSW implementation share an identical list of hyper-parameters:
310
+
311
+ - connectivity (often called `M`),
312
+ - expansion on additions (often called `efConstruction`),
313
+ - expansion on search (often called `ef`).
314
+
315
+ The default values vary drastically.
316
+
317
+ | Library | Connectivity | EF @ A | EF @ S |
318
+ | :-------: | :----------: | :----: | :----: |
319
+ | `hnswlib` | 16 | 200 | 10 |
320
+ | `FAISS` | 32 | 40 | 16 |
321
+ | `USearch` | 16 | 128 | 64 |
322
+
323
+
324
+ To achieve best results, please compile locally and check out various configuration options.
325
+
326
+ ```sh
327
+ cmake -B ./build_release \
328
+ -DCMAKE_BUILD_TYPE=Release \
329
+ -DUSEARCH_USE_OPENMP=1 \
330
+ -DUSEARCH_USE_JEMALLOC=1 && \
331
+ make -C ./build_release -j
332
+
333
+ ./build_release/bench --help
334
+ ```
335
+
336
+ Which would print the following instructions:
337
+
338
+ ```txt
339
+ SYNOPSIS
340
+ ./build_release/bench [--vectors <path>] [--queries <path>] [--neighbors <path>] [-b] [-j
341
+ <integer>] [-c <integer>] [--expansion-add <integer>]
342
+ [--expansion-search <integer>] [--native|--f16quant|--i8quant]
343
+ [--ip|--l2|--cos|--haversine] [-h]
344
+
345
+ OPTIONS
346
+ --vectors <path>
347
+ .fbin file path to construct the index
348
+
349
+ --queries <path>
350
+ .fbin file path to query the index
351
+
352
+ --neighbors <path>
353
+ .ibin file path with ground truth
354
+
355
+ -b, --big Will switch to uint40_t for neighbors lists with over 4B entries
356
+ -j, --threads <integer>
357
+ Uses all available cores by default
358
+
359
+ -c, --connectivity <integer>
360
+ Index granularity
361
+
362
+ --expansion-add <integer>
363
+ Affects indexing depth
364
+
365
+ --expansion-search <integer>
366
+ Affects search depth
367
+
368
+ --native Use raw templates instead of type-punned classes
369
+ --f16quant Enable `f16_t` quantization
370
+ --i8quant Enable `int8_t` quantization
371
+ --ip Choose Inner Product metric
372
+ --l2 Choose L2 Euclidean metric
373
+ --cos Choose Angular metric
374
+ --haversine Choose Haversine metric
375
+ -h, --help Print this help information on this tool and exit
376
+ ```
133
377
 
134
378
  ## TODO
135
379
 
136
380
  - JavaScript: Allow calling from "worker threads".
137
381
  - Rust: Allow passing a custom thread ID.
382
+
383
+ ## AI + Vector Search = Semantic Search
384
+
385
+ AI has a growing number of applications, but one of the coolest classic ideas is to use it for Semantic Search.
386
+ One can take an encoder model, like the multi-modal UForm, and a web-programming framework, like UCall, and build an image search platform in just 20 lines of Python.
387
+
388
+ ```python
389
+ import ucall.rich_posix as ucall
390
+ import uform
391
+ import usearch
392
+
393
+ import numpy as np
394
+ from PIL import Image
395
+
396
+ server = ucall.Server()
397
+ model = uform.get_model('unum-cloud/uform-vl-multilingual')
398
+ index = usearch.Index(dim=256)
399
+
400
+ @server
401
+ def add(label: int, photo: Image.Image):
402
+ image = model.preprocess_image(photo)
403
+ vector = model.encode_image(image).detach().numpy()
404
+ labels = np.array([label], dtype=np.longlong)
405
+ index.add(labels, vector, copy=True)
406
+
407
+ @server
408
+ def search(query: str) -> np.ndarray:
409
+ tokens = model.preprocess_text(query)
410
+ vector = model.encode_text(tokens).detach().numpy()
411
+ neighbors = index.search(vector, 3)
412
+ return neighbors[0][:neighbors[2][0]]
413
+
414
+ server.run()
415
+ ```
416
+
417
+ Check [that](https://github.com/ashvardanian/image-search) and [other](https://github.com/unum-cloud/examples) examples on our corporate GitHub 🤗
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.8
1
+ 0.1.9
@@ -19,7 +19,12 @@ public class IndexTest {
19
19
  public void test() {
20
20
  String path = "./tmp/";
21
21
  deleteDirectoryFiles(path);
22
- // new Index("cos", "f32", 1, 10, 1, 1, 1);
22
+
23
+ // Index index = new Index.Config().metric("cos").dimensions(2).build();
24
+ // float vec[] = {10, 20};
25
+ // index.add(42, vec);
26
+ // int[] labels = index.search(vec, 5);
27
+
23
28
  System.out.println("Java Tests Passed!");
24
29
  }
25
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "The Mighty Tiny Vector Search Engine with Automatic Quantization and Hardware Acceleration",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",
package/python/bench.py CHANGED
@@ -60,7 +60,7 @@ def measure(f) -> float:
60
60
  f()
61
61
  b = datetime.datetime.now()
62
62
  c = b - a
63
- print(f'Took: {c.seconds} seconds')
63
+ print(f'Took: {c.seconds:.2f} seconds')
64
64
  return c.seconds
65
65
 
66
66
 
@@ -71,12 +71,15 @@ def construct_both() -> tuple:
71
71
  index_meta.hnsw.efConstruction = efConstruction
72
72
  dt_meta = measure(lambda: index_meta.add(xb))
73
73
 
74
- index_unum = usearch.make_index(
74
+ index_unum = usearch.Index(
75
+ dim=d,
75
76
  expansion_add=efConstruction,
76
77
  expansion_search=efSearch,
77
78
  connectivity=M,
78
79
  )
79
- dt_unum = measure(lambda: index_unum.add(labels, xb, copy=False))
80
+ assert len(index_unum) == 0
81
+ dt_unum = measure(lambda: index_unum.add(labels, xb))
82
+ assert len(index_unum) == xb.shape[0]
80
83
 
81
84
  return dt_meta, dt_unum
82
85
 
package/python/test.py CHANGED
@@ -6,15 +6,14 @@ count_vectors = 100
6
6
  count_dimensions = 96
7
7
 
8
8
  index = usearch.Index(dim=96)
9
- print(index)
10
-
11
9
  vectors = np.random.uniform(
12
10
  0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
13
11
  labels = np.array(range(count_vectors), dtype=np.longlong)
14
- print('will insert', labels.shape, vectors.shape)
15
12
 
16
13
  index.add(labels, vectors, copy=True)
14
+ assert index.ndim == count_dimensions
15
+ assert index.capacity >= count_vectors
16
+ assert len(index) == count_vectors
17
17
 
18
- print('will search')
19
18
  results = index.search(vectors, 10)
20
19
  print('found', results[0].shape, results[1].shape, results[2].shape)
package/src/advanced.hpp CHANGED
@@ -127,17 +127,17 @@ void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callbac
127
127
  }
128
128
 
129
129
  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) {
130
+ std::size_t tasks_per_thread = tasks / threads + (tasks % threads) != 0;
131
+ for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
132
132
  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);
133
+ for (std::size_t task_idx = thread_idx * tasks_per_thread;
134
+ task_idx < std::min(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
135
+ callback(thread_idx, task_idx);
136
136
  });
137
137
  }
138
138
 
139
- for (std::size_t thread = 0; thread != threads; ++thread)
140
- threads_pool[thread].join();
139
+ for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx)
140
+ threads_pool[thread_idx].join();
141
141
  }
142
142
 
143
143
  /**
@@ -172,6 +172,16 @@ enum class accuracy_t {
172
172
  i8q100_k,
173
173
  };
174
174
 
175
+ inline char const* accuracy_name(accuracy_t accuracy) noexcept {
176
+ switch (accuracy) {
177
+ case accuracy_t::f32_k: return "f32";
178
+ case accuracy_t::f16_k: return "f16";
179
+ case accuracy_t::f64_k: return "f64";
180
+ case accuracy_t::i8q100_k: return "i8q100";
181
+ default: return "";
182
+ }
183
+ }
184
+
175
185
  inline bool str_equals(char const* begin, std::size_t len, char const* other_begin) noexcept {
176
186
  std::size_t other_len = strlen(other_begin);
177
187
  return len == other_len && strncmp(begin, other_begin, len) == 0;
@@ -217,13 +227,14 @@ enum class isa_t {
217
227
  avx512_k,
218
228
  };
219
229
 
220
- inline char const* isa(isa_t isa) noexcept {
230
+ inline char const* isa_name(isa_t isa) noexcept {
221
231
  switch (isa) {
222
232
  case isa_t::auto_k: return "auto";
223
233
  case isa_t::neon_k: return "neon";
224
234
  case isa_t::sve_k: return "sve";
225
235
  case isa_t::avx2_k: return "avx2";
226
236
  case isa_t::avx512_k: return "avx512";
237
+ default: return "";
227
238
  }
228
239
  }
229
240
 
@@ -445,7 +456,6 @@ class auto_index_gt {
445
456
  }
446
457
 
447
458
  static metric_and_meta_t ip_metric_f32(std::size_t dimensions) {
448
- #if 0
449
459
  #if defined(__x86_64__)
450
460
  if (dimensions % 4 == 0)
451
461
  return {
@@ -458,7 +468,7 @@ class auto_index_gt {
458
468
  if (supports_arm_sve())
459
469
  return {
460
470
  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);
471
+ return 1 - simsimd_dot_f32sve(a, b, d);
462
472
  }),
463
473
  isa_t::sve_k,
464
474
  };
@@ -469,13 +479,11 @@ class auto_index_gt {
469
479
  }),
470
480
  isa_t::neon_k,
471
481
  };
472
- #endif
473
482
  #endif
474
483
  return {pun_metric<f32_t>(ip_gt<f32_t>{}), isa_t::auto_k};
475
484
  }
476
485
 
477
486
  static metric_and_meta_t ip_metric_f16(std::size_t dimensions) {
478
- #if 0
479
487
  #if defined(__x86_64__)
480
488
  #elif defined(__aarch64__)
481
489
  if (supports_arm_sve())
@@ -492,7 +500,6 @@ class auto_index_gt {
492
500
  }),
493
501
  isa_t::neon_k,
494
502
  };
495
- #endif
496
503
  #endif
497
504
  return {pun_metric<f16_converted_t>(ip_gt<f16_converted_t>{}), isa_t::auto_k};
498
505
  }
package/src/bench.cpp CHANGED
@@ -295,8 +295,10 @@ void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
295
295
  if (args.quantize_i8)
296
296
  accuracy = accuracy_t::i8q100_k;
297
297
 
298
+ std::printf("-- Accuracy: %s\n", accuracy_name(accuracy));
299
+
298
300
  index_at index{type_punned_index_for_metric<index_at>(dataset, args, config, accuracy)};
299
- std::printf("- Hardware acceleration: %s\n", isa(index.acceleration()));
301
+ std::printf("- Hardware acceleration: %s\n", isa_name(index.acceleration()));
300
302
  std::printf("-- Will benchmark in-memory\n");
301
303
 
302
304
  single_shot(dataset, index, true);
@@ -410,6 +412,11 @@ int main(int argc, char** argv) {
410
412
  config.max_threads_add = config.max_threads_search = args.threads;
411
413
  config.max_elements = dataset.vectors_count();
412
414
 
415
+ std::printf("- Index: \n");
416
+ std::printf("-- Connectivity: %zu\n", config.connectivity);
417
+ std::printf("-- Expansion @ Add: %zu\n", config.expansion_add);
418
+ std::printf("-- Expansion @ Search: %zu\n", config.expansion_search);
419
+
413
420
  if (args.big)
414
421
  run_big_or_small<uint40_t>(dataset, args, config);
415
422
  else