usearch 2.1.3 → 2.2.1

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.
@@ -148,7 +148,10 @@ jobs:
148
148
  - uses: actions/setup-node@v3
149
149
  with:
150
150
  node-version: 18
151
- - run: npm install
151
+ - run: |
152
+ export CC=/usr/bin/gcc-12
153
+ export CXX=/usr/bin/g++-12
154
+ npm install
152
155
  - run: npm ci
153
156
  - run: npm test
154
157
 
@@ -176,7 +179,10 @@ jobs:
176
179
  - name: Setup Gradle
177
180
  uses: gradle/gradle-build-action@v2.4.2
178
181
  - name: Execute Gradle build
179
- run: gradle clean build
182
+ run:
183
+ export CC=/usr/bin/gcc-12
184
+ export CXX=/usr/bin/g++-12
185
+ gradle clean build
180
186
 
181
187
  test_swift:
182
188
  name: Test ObjC & Swift
@@ -251,7 +257,10 @@ jobs:
251
257
 
252
258
  - name: Build library
253
259
  run: |
254
- cd golang && make -C ../c libusearch_c.so
260
+ cd golang/
261
+ export CC=/usr/bin/gcc-12
262
+ export CXX=/usr/bin/g++-12
263
+ make -C ../c libusearch_c.so
255
264
  sudo mv ../c/libusearch_c.so /usr/local/lib/libusearch.a
256
265
  sudo mv ../c/usearch.h /usr/local/include/usearch.h
257
266
 
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "2.1.3"
3
+ version = "2.2.1"
4
4
  authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
package/conanfile.py CHANGED
@@ -4,7 +4,7 @@ from conans import ConanFile
4
4
  class USearchConan(ConanFile):
5
5
 
6
6
  name = 'USearch'
7
- version = '2.1.3'
7
+ version = '2.2.1'
8
8
  license = 'Apache 2.0'
9
9
  url = 'https://github.com/unum-cloud/usearch'
10
10
  description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
@@ -42,13 +42,9 @@
42
42
 
43
43
  #if USEARCH_USE_SIMSIMD
44
44
  #if __linux__
45
- #define SIMSIMD_TARGET_ARM_NEON 1
46
- #define SIMSIMD_TARGET_X86_AVX2 1
47
- #define SIMSIMD_TARGET_ARM_SVE 1
48
- #define SIMSIMD_TARGET_X86_AVX512 1
49
- #endif
50
45
  #include <simsimd/simsimd.h>
51
46
  #endif
47
+ #endif
52
48
 
53
49
  namespace unum {
54
50
  namespace usearch {
@@ -133,7 +129,9 @@ enum class isa_kind_t {
133
129
  neon_k,
134
130
  sve_k,
135
131
  avx2_k,
132
+ avx2f16_k,
136
133
  avx512_k,
134
+ avx512f16_k,
137
135
  };
138
136
 
139
137
  enum class prefetching_kind_t {
@@ -199,6 +197,8 @@ inline char const* isa_name(isa_kind_t isa_kind) noexcept {
199
197
  case isa_kind_t::sve_k: return "sve";
200
198
  case isa_kind_t::avx2_k: return "avx2";
201
199
  case isa_kind_t::avx512_k: return "avx512";
200
+ case isa_kind_t::avx2f16_k: return "avx2+f16";
201
+ case isa_kind_t::avx512f16_k: return "avx512+f16";
202
202
  default: return "";
203
203
  }
204
204
  }
@@ -217,12 +217,47 @@ inline bool hardware_supports(isa_kind_t isa_kind) noexcept {
217
217
  #endif
218
218
 
219
219
  // When compiling with GCC, one may use the "built-ins", including ones
220
- // designed for CPU capability detection.
221
- #if defined(USEARCH_DEFINED_X86) && defined(USEARCH_DEFINED_GCC)
222
- __builtin_cpu_init();
220
+ // designed for CPU capability detection, but they are not fully compatible with Clang.
221
+ // So instead I've decided to add a pinch of inline assembly.
222
+ #if defined(USEARCH_DEFINED_X86)
223
+ auto check_cpu_feature = [](std::uint64_t feature_mask, int function_id, int register_id) -> bool {
224
+ std::uint32_t eax, ebx, ecx, edx;
225
+ // Execute CPUID instruction and store results in eax, ebx, ecx, edx
226
+ // Setting %ecx to 0 as well
227
+ __asm__ volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(function_id), "c"(0));
228
+ std::uint32_t register_value;
229
+ switch (register_id) {
230
+ case 0: register_value = eax; break;
231
+ case 1: register_value = ebx; break;
232
+ case 2: register_value = ecx; break;
233
+ case 3: register_value = edx; break;
234
+ default: return false;
235
+ }
236
+
237
+ return (register_value & feature_mask) != 0;
238
+ };
239
+
240
+ // Check for AVX2 (Function ID 7, EBX register)
241
+ // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L148
242
+ bool supports_avx2 = check_cpu_feature(1 << 5, 7, 1);
243
+
244
+ // Check for F16C (Function ID 1, ECX register)
245
+ // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L107
246
+ bool supports_f16c = check_cpu_feature(1 << 29, 1, 2);
247
+
248
+ // Check for AVX512F (Function ID 7, EBX register)
249
+ // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L155
250
+ bool supports_avx512f = check_cpu_feature(1 << 16, 7, 1);
251
+
252
+ // Check for AVX512FP16 (Function ID 7, EDX register)
253
+ // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L198C9-L198C23
254
+ bool supports_avx512fp16 = check_cpu_feature(1 << 23, 7, 3);
255
+
223
256
  switch (isa_kind) {
224
- case isa_kind_t::avx2_k: return __builtin_cpu_supports("avx2");
225
- case isa_kind_t::avx512_k: return __builtin_cpu_supports("avx512f");
257
+ case isa_kind_t::avx2f16_k: return supports_avx2 && supports_f16c;
258
+ case isa_kind_t::avx512f16_k: return supports_avx512fp16 && supports_avx512f;
259
+ case isa_kind_t::avx2_k: return supports_avx2;
260
+ case isa_kind_t::avx512_k: return supports_avx512f;
226
261
  default: return false;
227
262
  }
228
263
  #endif
@@ -1404,20 +1439,59 @@ class metric_punned_t {
1404
1439
  static metric_punned_t cos_metric_f16_(std::size_t bytes_per_vector) {
1405
1440
  #if USEARCH_USE_SIMSIMD
1406
1441
  #if SIMSIMD_TARGET_X86_AVX512
1407
- if (hardware_supports(isa_kind_t::avx512_k) && bytes_per_vector % 32 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_cos_f16x16_avx512(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::avx512_k};
1442
+ if (hardware_supports(isa_kind_t::avx512f16_k) && bytes_per_vector % 32 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_cos_f16x16_avx512(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::avx512f16_k};
1443
+ #endif
1444
+ #if SIMSIMD_TARGET_X86_AVX2
1445
+ if (hardware_supports(isa_kind_t::avx2f16_k) && bytes_per_vector % 16 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_cos_f16x8_avx2(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::avx2f16_k};
1408
1446
  #endif
1409
1447
  #if SIMSIMD_TARGET_ARM_NEON
1410
1448
  if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 8 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_cos_f16x4_neon(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::neon_k};
1411
1449
  #endif
1450
+ #if SIMSIMD_TARGET_ARM_SVE
1451
+ if (hardware_supports(isa_kind_t::sve_k)) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_cos_f16_sve(a, b, bytes_per_vector / 4); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::sve_k};
1452
+ #endif
1412
1453
  #endif
1413
1454
  return {to_stl_<metric_cos_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
1414
1455
  }
1415
1456
 
1457
+ static metric_punned_t ip_metric_f16_(std::size_t bytes_per_vector) {
1458
+ #if USEARCH_USE_SIMSIMD
1459
+ #if SIMSIMD_TARGET_X86_AVX512
1460
+ if (hardware_supports(isa_kind_t::avx512f16_k) && bytes_per_vector % 32 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_dot_f16x16_avx512(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::avx512f16_k};
1461
+ #endif
1462
+ #if SIMSIMD_TARGET_X86_AVX2
1463
+ if (hardware_supports(isa_kind_t::avx2f16_k) && bytes_per_vector % 16 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_dot_f16x8_avx2(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::avx2f16_k};
1464
+ #endif
1465
+ #if SIMSIMD_TARGET_ARM_NEON
1466
+ if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 8 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_dot_f16x4_neon(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::neon_k};
1467
+ #endif
1468
+ #endif
1469
+ return {to_stl_<metric_cos_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
1470
+ }
1471
+
1472
+ static metric_punned_t l2sq_metric_f16_(std::size_t bytes_per_vector) {
1473
+ #if USEARCH_USE_SIMSIMD
1474
+ #if SIMSIMD_TARGET_X86_AVX512
1475
+ if (hardware_supports(isa_kind_t::avx512f16_k) && bytes_per_vector % 32 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_l2sq_f16x16_avx512(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::avx512f16_k};
1476
+ #endif
1477
+ #if SIMSIMD_TARGET_X86_AVX2
1478
+ if (hardware_supports(isa_kind_t::avx2f16_k) && bytes_per_vector % 16 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_l2sq_f16x8_avx2(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::avx2f16_k};
1479
+ #endif
1480
+ #if SIMSIMD_TARGET_ARM_SVE
1481
+ if (hardware_supports(isa_kind_t::sve_k)) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_l2sq_f16_sve(a, b, bytes_per_vector / 4); }), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f16_k, isa_kind_t::sve_k};
1482
+ #endif
1483
+ #endif
1484
+ return {to_stl_<metric_l2sq_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
1485
+ }
1486
+
1416
1487
  static metric_punned_t cos_metric_i8_(std::size_t bytes_per_vector) {
1417
1488
  #if USEARCH_USE_SIMSIMD
1418
1489
  #if SIMSIMD_TARGET_ARM_NEON
1419
1490
  if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 16 == 0) return {pun_stl_<int8_t>([=](int8_t const* a, int8_t const* b) { return simsimd_cos_i8x16_neon(a, b, bytes_per_vector); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::neon_k};
1420
1491
  #endif
1492
+ #if SIMSIMD_TARGET_X86_AVX2
1493
+ if (hardware_supports(isa_kind_t::avx2_k) && bytes_per_vector % 32 == 0) return {pun_stl_<int8_t>([=](int8_t const* a, int8_t const* b) { return simsimd_cos_i8x32_avx2(a, b, bytes_per_vector); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::avx2_k};
1494
+ #endif
1421
1495
  #endif
1422
1496
  return {to_stl_<cos_i8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
1423
1497
  }
@@ -1425,7 +1499,7 @@ class metric_punned_t {
1425
1499
  static metric_punned_t ip_metric_(std::size_t bytes_per_vector, scalar_kind_t scalar_kind) {
1426
1500
  switch (scalar_kind) { // The two most common numeric types for the most common metric have optimized versions
1427
1501
  case scalar_kind_t::f32_k: return ip_metric_f32_(bytes_per_vector);
1428
- case scalar_kind_t::f16_k: return cos_metric_f16_(bytes_per_vector); // Dot-product accumulates error, Cosine-distance normalizes it
1502
+ case scalar_kind_t::f16_k: return ip_metric_f16_(bytes_per_vector);
1429
1503
  case scalar_kind_t::i8_k: return cos_metric_i8_(bytes_per_vector);
1430
1504
  case scalar_kind_t::f64_k: return {to_stl_<metric_ip_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
1431
1505
  default: return {};
@@ -1435,7 +1509,7 @@ class metric_punned_t {
1435
1509
  static metric_punned_t l2sq_metric_(std::size_t bytes_per_vector, scalar_kind_t scalar_kind) {
1436
1510
  switch (scalar_kind) {
1437
1511
  case scalar_kind_t::i8_k: return {to_stl_<l2sq_i8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
1438
- case scalar_kind_t::f16_k: return {to_stl_<metric_l2sq_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
1512
+ case scalar_kind_t::f16_k: return l2sq_metric_f16_(bytes_per_vector);
1439
1513
  case scalar_kind_t::f32_k: return {to_stl_<metric_l2sq_gt<f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f32_k, isa_kind_t::auto_k};
1440
1514
  case scalar_kind_t::f64_k: return {to_stl_<metric_l2sq_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
1441
1515
  default: return {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.1.3",
3
+ "version": "2.2.1",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian",
6
6
  "license": "Apache 2.0",
package/python/lib.cpp CHANGED
@@ -957,6 +957,17 @@ PYBIND11_MODULE(compiled, m) {
957
957
  py::arg("metric_pointer") = 0 //
958
958
  );
959
959
 
960
+ m.def(
961
+ "hardware_acceleration",
962
+ [](scalar_kind_t scalar_kind, std::size_t dimensions, metric_kind_t metric_kind) -> bool {
963
+ return metric_t(dimensions, metric_kind, scalar_kind).isa_kind() != isa_kind_t::auto_k;
964
+ },
965
+ py::kw_only(), //
966
+ py::arg("dtype") = scalar_kind_t::f32_k, //
967
+ py::arg("ndim") = 0, //
968
+ py::arg("metric_kind") = metric_kind_t::cos_k //
969
+ );
970
+
960
971
  auto i = py::class_<dense_index_py_t, std::shared_ptr<dense_index_py_t>>(m, "Index");
961
972
 
962
973
  i.def(py::init(&make_index), //
@@ -29,6 +29,7 @@ from usearch.compiled import (
29
29
  index_dense_metadata_from_path as _index_dense_metadata_from_path,
30
30
  index_dense_metadata_from_buffer as _index_dense_metadata_from_buffer,
31
31
  exact_search as _exact_search,
32
+ hardware_acceleration as _hardware_acceleration,
32
33
  )
33
34
 
34
35
  # Precompiled symbols that will be exposed
@@ -72,9 +73,17 @@ NoneType = type(None)
72
73
  PathOrBuffer = Union[str, os.PathLike, bytes]
73
74
 
74
75
 
75
- def _normalize_dtype(dtype, metric: MetricKind = MetricKind.Cos) -> ScalarKind:
76
+ def _normalize_dtype(
77
+ dtype,
78
+ ndim: int = 0,
79
+ metric: MetricKind = MetricKind.Cos,
80
+ ) -> ScalarKind:
76
81
  if dtype is None or dtype == "":
77
- return ScalarKind.B1 if metric in MetricKindBitwise else ScalarKind.F32
82
+ if metric in MetricKindBitwise:
83
+ return ScalarKind.B1
84
+ if _hardware_acceleration(dtype=ScalarKind.F16, ndim=ndim, metric_kind=metric):
85
+ return ScalarKind.F16
86
+ return ScalarKind.F32
78
87
 
79
88
  if isinstance(dtype, ScalarKind):
80
89
  return dtype
@@ -491,8 +500,12 @@ class Index:
491
500
  :type dtype: Optional[DTypeLike], defaults to None
492
501
  For continuous metrics can be: f16, f32, f64, or i8.
493
502
  For bitwise metrics it's implementation-defined, and can't change.
503
+ If nothing is provided, the optimal data type is selected based on the metric
504
+ kind and hardware support.
494
505
  Example: you can use the `f16` index with `f32` vectors in Euclidean space,
495
- which will be automatically downcasted.
506
+ which will be automatically downcasted. Moreover, if `dtype=None` is passed,
507
+ and hardware supports `f16` SIMD-instructions, this choice will be done for you.
508
+ You can later double-check the used representation with `index.dtype`.
496
509
 
497
510
  :param connectivity: Connections per node in HNSW
498
511
  :type connectivity: Optional[int], defaults to None
@@ -548,7 +561,7 @@ class Index:
548
561
  )
549
562
 
550
563
  # Validate, that the right scalar type is defined
551
- dtype = _normalize_dtype(dtype, self._metric_kind)
564
+ dtype = _normalize_dtype(dtype, ndim, self._metric_kind)
552
565
  self._compiled = _CompiledIndex(
553
566
  ndim=ndim,
554
567
  dtype=dtype,
@@ -23,7 +23,7 @@ jobs:
23
23
  strategy:
24
24
  fail-fast: false
25
25
  matrix:
26
- os: [ubuntu-22.04] # macOS-11, windows-2022
26
+ os: [ubuntu-22.04, macOS-11] # windows-2022
27
27
  python-version: ["3.11"]
28
28
 
29
29
  steps:
@@ -35,7 +35,7 @@ jobs:
35
35
  python-version: ${{ matrix.python-version }}
36
36
 
37
37
  - name: Set up Clang
38
- run: brew install llvm
38
+ run: brew install gcc@12
39
39
  if: ${{ matrix.os == 'macOS-11' }}
40
40
 
41
41
  - name: Set up MinGW
@@ -59,10 +59,8 @@ jobs:
59
59
 
60
60
  - name: Build locally on MacOS
61
61
  run: |
62
- export LDFLAGS="-L/usr/local/opt/llvm/lib"
63
- export CPPFLAGS="-I/usr/local/opt/llvm/include"
64
- export CC=/usr/local/opt/llvm/bin/clang
65
- export CXX=/usr/local/opt/llvm/bin/clang++
62
+ export CC=/usr/local/bin/gcc-12
63
+ export CXX=/usr/local/bin/g++-12
66
64
  python -m pip install .
67
65
  if: ${{ matrix.os == 'macOS-11' }}
68
66
 
@@ -57,8 +57,7 @@ jobs:
57
57
  needs: versioning
58
58
  strategy:
59
59
  matrix:
60
- os: [ubuntu-20.04] # macOS-11, windows-2022
61
-
60
+ os: [ubuntu-20.04, macOS-11] # windows-2022
62
61
  steps:
63
62
  - uses: actions/checkout@v3
64
63
  with:
@@ -76,8 +75,15 @@ jobs:
76
75
  - name: Install CIBuildWheel
77
76
  run: python -m pip install --no-cache-dir cibuildwheel
78
77
 
79
- - name: Build wheels
78
+ - name: Build wheels on Ubuntu
79
+ run: python -m cibuildwheel
80
+ if: matrix.os == 'ubuntu-20.04'
81
+
82
+ - name: Build wheels on MacOS
80
83
  run: python -m cibuildwheel
84
+ env:
85
+ CIBW_ENVIRONMENT: CC=/usr/local/bin/gcc-12 CXX=/usr/local/bin/g++-12
86
+ if: matrix.os == 'macOS-11'
81
87
 
82
88
  - uses: actions/upload-artifact@v3
83
89
  with:
@@ -45,6 +45,9 @@
45
45
  #endif
46
46
  #endif
47
47
 
48
+ #undef SIMSIMD_TARGET_X86
49
+ #undef SIMSIMD_TARGET_X86_AVX2
50
+ #undef SIMSIMD_TARGET_X86_AVX512
48
51
  #define SIMSIMD_TARGET_X86 0
49
52
  #define SIMSIMD_TARGET_X86_AVX2 0
50
53
  #define SIMSIMD_TARGET_X86_AVX512 0
@@ -71,6 +74,9 @@
71
74
  #endif
72
75
  #endif
73
76
 
77
+ #undef SIMSIMD_TARGET_ARM
78
+ #undef SIMSIMD_TARGET_ARM_NEON
79
+ #undef SIMSIMD_TARGET_ARM_SVE
74
80
  #define SIMSIMD_TARGET_ARM 0
75
81
  #define SIMSIMD_TARGET_ARM_NEON 0
76
82
  #define SIMSIMD_TARGET_ARM_SVE 0
@@ -143,7 +149,7 @@ simsimd_cos_f32_sve(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
143
149
  simsimd_f32_t ab = svaddv_f32(svptrue_b32(), ab_vec);
144
150
  simsimd_f32_t a2 = svaddv_f32(svptrue_b32(), a2_vec);
145
151
  simsimd_f32_t b2 = svaddv_f32(svptrue_b32(), b2_vec);
146
- return 1 - ab / (sqrt(a2) * sqrt(b2));
152
+ return 1 - ab / (sqrtf(a2) * sqrtf(b2));
147
153
  }
148
154
 
149
155
  __attribute__((target("+sve"))) inline static simsimd_f32_t //
@@ -160,7 +166,7 @@ simsimd_l2sq_f32_sve(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
160
166
  pg_vec = svwhilelt_b32(i, d);
161
167
  } while (svptest_any(svptrue_b32(), pg_vec));
162
168
  simsimd_f32_t d2 = svaddv_f32(svptrue_b32(), d2_vec);
163
- return sqrt(d2);
169
+ return d2;
164
170
  }
165
171
 
166
172
  __attribute__((target("+sve+fp16"))) inline static simsimd_f32_t //
@@ -184,7 +190,7 @@ simsimd_cos_f16_sve(simsimd_f16_t const* a_enum, simsimd_f16_t const* b_enum, si
184
190
  simsimd_f16_t ab = svaddv_f16(svptrue_b16(), ab_vec);
185
191
  simsimd_f16_t a2 = svaddv_f16(svptrue_b16(), a2_vec);
186
192
  simsimd_f16_t b2 = svaddv_f16(svptrue_b16(), b2_vec);
187
- return 1 - ab / (sqrt(a2) * sqrt(b2));
193
+ return 1 - ab / (sqrtf(a2) * sqrtf(b2));
188
194
  }
189
195
 
190
196
  __attribute__((target("+sve+fp16"))) inline static simsimd_f32_t //
@@ -235,6 +241,18 @@ simsimd_dot_f32x4_neon(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d)
235
241
  return 1 - vaddvq_f32(ab_vec);
236
242
  }
237
243
 
244
+ __attribute__((target("+simd+fp16"))) inline static simsimd_f32_t //
245
+ simsimd_dot_f16x4_neon(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
246
+
247
+ float32x4_t ab_vec = vdupq_n_f32(0);
248
+ for (size_t i = 0; i != d; i += 4) {
249
+ float32x4_t a_vec = vcvt_f32_f16(vld1_f16((float16_t const*)a + i));
250
+ float32x4_t b_vec = vcvt_f32_f16(vld1_f16((float16_t const*)b + i));
251
+ ab_vec = vfmaq_f32(ab_vec, a_vec, b_vec);
252
+ }
253
+ return 1 - vaddvq_f32(ab_vec);
254
+ }
255
+
238
256
  __attribute__((target("+simd+fp16"))) inline static simsimd_f32_t //
239
257
  simsimd_cos_f16x4_neon(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
240
258
 
@@ -252,7 +270,7 @@ simsimd_cos_f16x4_neon(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d)
252
270
  simsimd_f32_t ab = vaddvq_f32(ab_vec);
253
271
  simsimd_f32_t a2 = vaddvq_f32(a2_vec);
254
272
  simsimd_f32_t b2 = vaddvq_f32(b2_vec);
255
- return 1 - ab / (sqrt(a2) * sqrt(b2));
273
+ return 1 - ab / (sqrtf(a2) * sqrtf(b2));
256
274
  }
257
275
 
258
276
  __attribute__((target("+simd"))) inline static simsimd_f32_t //
@@ -290,7 +308,7 @@ simsimd_cos_i8x16_neon(int8_t const* a, int8_t const* b, size_t d) {
290
308
  int32_t a2 = vget_lane_s32(vpadd_s32(a2_part, a2_part), 0);
291
309
  int32x2_t b2_part = vadd_s32(vget_high_s32(b2_vec), vget_low_s32(b2_vec));
292
310
  int32_t b2 = vget_lane_s32(vpadd_s32(b2_part, b2_part), 0);
293
- return 1 - ab / (sqrt(a2) * sqrt(b2));
311
+ return 1 - ab / (sqrtf(a2) * sqrtf(b2));
294
312
  }
295
313
 
296
314
  __attribute__((target("+simd"))) inline static simsimd_f32_t //
@@ -308,7 +326,7 @@ simsimd_l2sq_i8x16_neon(int8_t const* a, int8_t const* b, size_t d) {
308
326
 
309
327
  int32x2_t d2_part = vadd_s32(vget_high_s32(d2_vec), vget_low_s32(d2_vec));
310
328
  int32_t d2 = vget_lane_s32(vpadd_s32(d2_part, d2_part), 0);
311
- return sqrt(d2);
329
+ return d2;
312
330
  }
313
331
 
314
332
  __attribute__((target("+simd"))) inline static simsimd_f32_t //
@@ -327,7 +345,7 @@ simsimd_cos_f32x4_neon(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d)
327
345
  simsimd_f32_t ab = vaddvq_f32(ab_vec);
328
346
  simsimd_f32_t a2 = vaddvq_f32(a2_vec);
329
347
  simsimd_f32_t b2 = vaddvq_f32(b2_vec);
330
- return 1 - ab / (sqrt(a2) * sqrt(b2));
348
+ return 1 - ab / (sqrtf(a2) * sqrtf(b2));
331
349
  }
332
350
 
333
351
  __attribute__((target("+sve"))) inline static simsimd_f32_t //
@@ -349,7 +367,7 @@ simsimd_hamming_b1x128_sve(uint8_t const* a, uint8_t const* b, size_t d) {
349
367
  #if SIMSIMD_TARGET_X86
350
368
  #if SIMSIMD_TARGET_X86_AVX2
351
369
 
352
- __attribute__((target("avx2"))) //
370
+ __attribute__((target("avx2"))) //
353
371
  __attribute__((target("fma"))) inline static simsimd_f32_t //
354
372
  simsimd_dot_f32x4_avx2(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
355
373
  __m128 ab_vec = _mm_set1_ps(0);
@@ -362,7 +380,7 @@ simsimd_dot_f32x4_avx2(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d)
362
380
  return 1 - ab_union.f;
363
381
  }
364
382
 
365
- __attribute__((target("avx2"))) //
383
+ __attribute__((target("avx2"))) //
366
384
  __attribute__((target("fma"))) inline static simsimd_f32_t //
367
385
  simsimd_cos_f32x4_avx2(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
368
386
 
@@ -385,13 +403,151 @@ simsimd_cos_f32x4_avx2(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d)
385
403
  union simsimd_f32i32_t ab_union = {_mm_cvtsi128_si32(_mm_castps_si128(ab_vec))};
386
404
  union simsimd_f32i32_t a2_union = {_mm_cvtsi128_si32(_mm_castps_si128(a2_vec))};
387
405
  union simsimd_f32i32_t b2_union = {_mm_cvtsi128_si32(_mm_castps_si128(b2_vec))};
388
- return 1 - ab_union.f / (sqrt(a2_union.f) * sqrt(b2_union.f));
406
+ return 1 - ab_union.f / (sqrtf(a2_union.f) * sqrtf(b2_union.f));
407
+ }
408
+
409
+ __attribute__((target("avx2"))) //
410
+ __attribute__((target("fma"))) inline static simsimd_f32_t //
411
+ simsimd_l2sq_f32x4_avx2(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
412
+ __m128 sum_vec = _mm_set1_ps(0);
413
+ for (size_t i = 0; i != d; i += 4) {
414
+ __m128 diff_vec = _mm_sub_ps(_mm_loadu_ps(a + i), _mm_loadu_ps(b + i));
415
+ sum_vec = _mm_fmadd_ps(diff_vec, diff_vec, sum_vec);
416
+ }
417
+ sum_vec = _mm_hadd_ps(sum_vec, sum_vec);
418
+ sum_vec = _mm_hadd_ps(sum_vec, sum_vec);
419
+ float result[1];
420
+ _mm_store_ss(result, sum_vec);
421
+ return result[0];
422
+ }
423
+
424
+ __attribute__((target("avx2"))) //
425
+ __attribute__((target("f16c"))) //
426
+ __attribute__((target("fma"))) inline static simsimd_f32_t //
427
+ simsimd_dot_f16x8_avx2(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
428
+ __m256 ab_vec = _mm256_set1_ps(0);
429
+ for (size_t i = 0; i != d; i += 8) {
430
+ __m256 a_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(a + i)));
431
+ __m256 b_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(b + i)));
432
+ ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
433
+ }
434
+ ab_vec = _mm256_add_ps(_mm256_permute2f128_ps(ab_vec, ab_vec, 0x81), ab_vec);
435
+ ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
436
+ ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
437
+ float result[1];
438
+ _mm_store_ss(result, _mm256_castps256_ps128(ab_vec));
439
+ return 1 - result[0];
440
+ }
441
+
442
+ __attribute__((target("avx2"))) //
443
+ __attribute__((target("f16c"))) //
444
+ __attribute__((target("fma"))) inline static simsimd_f32_t //
445
+ simsimd_cos_f16x8_avx2(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
446
+ __m256 ab_vec = _mm256_set1_ps(0);
447
+ __m256 a2_vec = _mm256_set1_ps(0);
448
+ __m256 b2_vec = _mm256_set1_ps(0);
449
+ for (size_t i = 0; i != d; i += 8) {
450
+ __m256 a_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(a + i)));
451
+ __m256 b_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(b + i)));
452
+ ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
453
+ a2_vec = _mm256_fmadd_ps(a_vec, a_vec, a2_vec);
454
+ b2_vec = _mm256_fmadd_ps(b_vec, b_vec, b2_vec);
455
+ }
456
+ ab_vec = _mm256_add_ps(_mm256_permute2f128_ps(ab_vec, ab_vec, 0x81), ab_vec);
457
+ ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
458
+ ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
459
+ a2_vec = _mm256_add_ps(_mm256_permute2f128_ps(a2_vec, a2_vec, 0x81), a2_vec);
460
+ a2_vec = _mm256_hadd_ps(a2_vec, a2_vec);
461
+ a2_vec = _mm256_hadd_ps(a2_vec, a2_vec);
462
+ b2_vec = _mm256_add_ps(_mm256_permute2f128_ps(b2_vec, b2_vec, 0x81), b2_vec);
463
+ b2_vec = _mm256_hadd_ps(b2_vec, b2_vec);
464
+ b2_vec = _mm256_hadd_ps(b2_vec, b2_vec);
465
+ float ab_result[1], a2_result[1], b2_result[1];
466
+ _mm_store_ss(ab_result, _mm256_castps256_ps128(ab_vec));
467
+ _mm_store_ss(a2_result, _mm256_castps256_ps128(a2_vec));
468
+ _mm_store_ss(b2_result, _mm256_castps256_ps128(b2_vec));
469
+ return 1 - ab_result[0] / (sqrtf(a2_result[0]) * sqrtf(b2_result[0]));
470
+ }
471
+
472
+ __attribute__((target("avx2"))) //
473
+ __attribute__((target("f16c"))) //
474
+ __attribute__((target("fma"))) inline static simsimd_f32_t //
475
+ simsimd_l2sq_f16x8_avx2(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
476
+ __m256 sum_vec = _mm256_set1_ps(0);
477
+ for (size_t i = 0; i != d; i += 8) {
478
+ __m256 a_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(a + i)));
479
+ __m256 b_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(b + i)));
480
+ __m256 diff_vec = _mm256_sub_ps(a_vec, b_vec);
481
+ sum_vec = _mm256_fmadd_ps(diff_vec, diff_vec, sum_vec);
482
+ }
483
+ sum_vec = _mm256_add_ps(_mm256_permute2f128_ps(sum_vec, sum_vec, 0x81), sum_vec);
484
+ sum_vec = _mm256_hadd_ps(sum_vec, sum_vec);
485
+ sum_vec = _mm256_hadd_ps(sum_vec, sum_vec);
486
+ float result[1];
487
+ _mm_store_ss(result, _mm256_castps256_ps128(sum_vec));
488
+ return result[0];
489
+ }
490
+
491
+ __attribute__((target("avx2"))) //
492
+ inline static simsimd_f32_t
493
+ simsimd_cos_i8x32_avx2(int8_t const* a, int8_t const* b, size_t d) {
494
+
495
+ __m256i ab_vec = _mm256_setzero_si256();
496
+ __m256i a2_vec = _mm256_setzero_si256();
497
+ __m256i b2_vec = _mm256_setzero_si256();
498
+
499
+ for (size_t i = 0; i != d; i += 32) {
500
+ __m256i a_vec = _mm256_loadu_si256((__m256i const*)(a + i));
501
+ __m256i b_vec = _mm256_loadu_si256((__m256i const*)(b + i));
502
+
503
+ // Multiply and add packed 8-bit integers
504
+ __m256i ab_part_vec = _mm256_maddubs_epi16(a_vec, b_vec);
505
+ __m256i a2_part_vec = _mm256_maddubs_epi16(a_vec, a_vec);
506
+ __m256i b2_part_vec = _mm256_maddubs_epi16(b_vec, b_vec);
507
+
508
+ ab_vec = _mm256_add_epi32(ab_vec, ab_part_vec);
509
+ a2_vec = _mm256_add_epi32(a2_vec, a2_part_vec);
510
+ b2_vec = _mm256_add_epi32(b2_vec, b2_part_vec);
511
+ }
512
+
513
+ // Horizontal sum across the 256-bit register
514
+ __m128i ab_low = _mm256_extracti128_si256(ab_vec, 0);
515
+ __m128i ab_high = _mm256_extracti128_si256(ab_vec, 1);
516
+ __m128i ab_sum = _mm_add_epi32(ab_low, ab_high);
517
+
518
+ __m128i a2_low = _mm256_extracti128_si256(a2_vec, 0);
519
+ __m128i a2_high = _mm256_extracti128_si256(a2_vec, 1);
520
+ __m128i a2_sum = _mm_add_epi32(a2_low, a2_high);
521
+
522
+ __m128i b2_low = _mm256_extracti128_si256(b2_vec, 0);
523
+ __m128i b2_high = _mm256_extracti128_si256(b2_vec, 1);
524
+ __m128i b2_sum = _mm_add_epi32(b2_low, b2_high);
525
+
526
+ // Further reduce to a single sum for each vector
527
+ int ab = _mm_extract_epi32(_mm_hadd_epi32(_mm_hadd_epi32(ab_sum, ab_sum), ab_sum), 0);
528
+ int a2 = _mm_extract_epi32(_mm_hadd_epi32(_mm_hadd_epi32(a2_sum, a2_sum), a2_sum), 0);
529
+ int b2 = _mm_extract_epi32(_mm_hadd_epi32(_mm_hadd_epi32(b2_sum, b2_sum), b2_sum), 0);
530
+
531
+ return 1 - ab / (sqrtf(a2) * sqrtf(b2));
389
532
  }
390
533
 
391
534
  #endif // SIMSIMD_TARGET_X86_AVX2
392
535
 
393
536
  #if SIMSIMD_TARGET_X86_AVX512
394
537
 
538
+ __attribute__((target("avx512fp16"))) //
539
+ __attribute__((target("avx512f"))) inline static simsimd_f32_t //
540
+ simsimd_dot_f16x16_avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
541
+ __m512 ab_vec = _mm512_set1_ps(0);
542
+ for (size_t i = 0; i != d; i += 16) {
543
+ __m512 a_vec = _mm512_cvtxph_ps(_mm256_loadu_ph(a + i));
544
+ __m512 b_vec = _mm512_cvtxph_ps(_mm256_loadu_ph(b + i));
545
+ ab_vec = _mm512_fmadd_ps(a_vec, b_vec, ab_vec);
546
+ }
547
+ simsimd_f32_t ab = _mm512_reduce_add_ps(ab_vec);
548
+ return 1 - ab;
549
+ }
550
+
395
551
  __attribute__((target("avx512fp16"))) //
396
552
  __attribute__((target("avx512f"))) inline static simsimd_f32_t //
397
553
  simsimd_cos_f16x16_avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
@@ -408,7 +564,21 @@ simsimd_cos_f16x16_avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t
408
564
  simsimd_f32_t ab = _mm512_reduce_add_ps(ab_vec);
409
565
  simsimd_f32_t a2 = _mm512_reduce_add_ps(a2_vec);
410
566
  simsimd_f32_t b2 = _mm512_reduce_add_ps(b2_vec);
411
- return 1 - ab / (sqrt(a2) * sqrt(b2));
567
+ return 1 - ab / (sqrtf(a2) * sqrtf(b2));
568
+ }
569
+
570
+ __attribute__((target("avx512fp16"))) //
571
+ __attribute__((target("avx512f"))) inline static simsimd_f32_t //
572
+ simsimd_l2sq_f16x16_avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
573
+ __m512 sum_vec = _mm512_set1_ps(0);
574
+ for (size_t i = 0; i != d; i += 16) {
575
+ __m512 a_vec = _mm512_cvtxph_ps(_mm256_loadu_ph(a + i));
576
+ __m512 b_vec = _mm512_cvtxph_ps(_mm256_loadu_ph(b + i));
577
+ __m512 diff_vec = _mm512_sub_ps(a_vec, b_vec);
578
+ sum_vec = _mm512_fmadd_ps(diff_vec, diff_vec, sum_vec);
579
+ }
580
+ simsimd_f32_t sum = _mm512_reduce_add_ps(sum_vec);
581
+ return sum;
412
582
  }
413
583
 
414
584
  __attribute__((target("avx512vpopcntdq"))) //
@@ -30,8 +30,9 @@ repair-wheel-command = "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
30
30
  [tool.cibuildwheel.macos]
31
31
  before-build = [
32
32
  "rm -rf {project}/build",
33
+ "brew install gcc@12",
33
34
  ]
34
- archs = ["x86_64", "universal2", "arm64"]
35
+ archs = ["x86_64", "arm64"]
35
36
  repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
36
37
 
37
38
  [tool.cibuildwheel.windows]
@@ -58,6 +58,8 @@ PyMODINIT_FUNC PyInit_simsimd(void) {
58
58
  return NULL;
59
59
 
60
60
  PyModule_AddObject(m, "tanimoto_b1x8_naive", distance(&simsimd_tanimoto_b1x8_naive));
61
+
62
+ // Specialized:
61
63
  PyModule_AddObject(m, "tanimoto_maccs_naive", distance(&simsimd_tanimoto_maccs_naive));
62
64
  PyModule_AddObject(m, "tanimoto_conditional_naive", distance(&simsimd_tanimoto_conditional_naive));
63
65
 
@@ -66,6 +68,8 @@ PyMODINIT_FUNC PyInit_simsimd(void) {
66
68
  PyModule_AddObject(m, "cos_f16x4_neon", distance(&simsimd_cos_f16x4_neon));
67
69
  PyModule_AddObject(m, "cos_i8x16_neon", distance(&simsimd_cos_i8x16_neon));
68
70
  PyModule_AddObject(m, "cos_f32x4_neon", distance(&simsimd_cos_f32x4_neon));
71
+
72
+ // Specialized:
69
73
  PyModule_AddObject(m, "tanimoto_maccs_neon", distance(&simsimd_tanimoto_maccs_neon));
70
74
 
71
75
  #if SIMSIMD_TARGET_ARM_SVE
@@ -76,16 +80,28 @@ PyMODINIT_FUNC PyInit_simsimd(void) {
76
80
  PyModule_AddObject(m, "l2sq_f16_sve", distance(&simsimd_l2sq_f16_sve));
77
81
  PyModule_AddObject(m, "hamming_b1x8_sve", distance(&simsimd_hamming_b1x8_sve));
78
82
  PyModule_AddObject(m, "hamming_b1x128_sve", distance(&simsimd_hamming_b1x128_sve));
83
+
84
+ // Specialized:
79
85
  PyModule_AddObject(m, "tanimoto_maccs_sve", distance(&simsimd_tanimoto_maccs_sve));
80
86
  #endif
81
87
  #endif
82
88
 
83
89
  #if SIMSIMD_TARGET_X86
90
+ PyModule_AddObject(m, "dot_f32x4_avx2", distance(&simsimd_dot_f32x4_avx2));
84
91
  PyModule_AddObject(m, "cos_f32x4_avx2", distance(&simsimd_cos_f32x4_avx2));
92
+ PyModule_AddObject(m, "l2sq_f32x4_avx2", distance(&simsimd_l2sq_f32x4_avx2));
93
+ PyModule_AddObject(m, "dot_f16x8_avx2", distance(&simsimd_dot_f16x8_avx2));
94
+ PyModule_AddObject(m, "cos_f16x8_avx2", distance(&simsimd_cos_f16x8_avx2));
95
+ PyModule_AddObject(m, "l2sq_f16x8_avx2", distance(&simsimd_l2sq_f16x8_avx2));
85
96
 
86
97
  #if SIMSIMD_TARGET_X86_AVX512
98
+ PyModule_AddObject(m, "dot_f16x16_avx512", distance(&simsimd_dot_f16x16_avx512));
87
99
  PyModule_AddObject(m, "cos_f16x16_avx512", distance(&simsimd_cos_f16x16_avx512));
100
+ PyModule_AddObject(m, "l2sq_f16x16_avx512", distance(&simsimd_l2sq_f16x16_avx512));
101
+
88
102
  PyModule_AddObject(m, "hamming_b1x128_avx512", distance(&simsimd_hamming_b1x128_avx512));
103
+
104
+ // Specialized:
89
105
  PyModule_AddObject(m, "tanimoto_maccs_avx512", distance(&simsimd_tanimoto_maccs_avx512));
90
106
  #endif
91
107
  #endif
package/wasmer.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name="unum/usearch"
3
- version="2.1.3"
3
+ version="2.2.1"
4
4
  description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
5
  license="Apache-2.0"
6
6
  readme="README.md"