usearch 2.8.1 → 2.8.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/CITATION.cff CHANGED
@@ -5,7 +5,7 @@ authors:
5
5
  given-names: "Ash"
6
6
  orcid: "https://orcid.org/0000-0002-4882-1815"
7
7
  title: "USearch by Unum Cloud"
8
- version: 2.8.1
8
+ version: 2.8.2
9
9
  doi: 10.5281/zenodo.7949416
10
10
  date-released: 2023-10-22
11
11
  url: "https://github.com/unum-cloud/usearch"
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "2.8.1"
3
+ version = "2.8.2"
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/README.md CHANGED
@@ -392,7 +392,7 @@ doi = {10.5281/zenodo.7949416},
392
392
  author = {Vardanian, Ash},
393
393
  title = {{USearch by Unum Cloud}},
394
394
  url = {https://github.com/unum-cloud/usearch},
395
- version = {2.8.1},
395
+ version = {2.8.2},
396
396
  year = {2023},
397
397
  month = oct,
398
398
  }
package/conanfile.py CHANGED
@@ -7,7 +7,7 @@ required_conan_version = ">=1.53.0"
7
7
  class USearchConan(ConanFile):
8
8
 
9
9
  name = "usearch"
10
- version = '2.8.1'
10
+ version = '2.8.2'
11
11
  license = "Apache-2.0"
12
12
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
13
13
  homepage = "https://github.com/unum-cloud/usearch"
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <Project>
3
3
  <PropertyGroup>
4
- <Version Condition="'$(Version)' == ''">2.8.1</Version>
4
+ <Version Condition="'$(Version)' == ''">2.8.2</Version>
5
5
 
6
6
  <Authors>Unum</Authors>
7
7
  <Company>Unum</Company>
@@ -11,7 +11,7 @@
11
11
 
12
12
  #define USEARCH_VERSION_MAJOR 2
13
13
  #define USEARCH_VERSION_MINOR 8
14
- #define USEARCH_VERSION_PATCH 1
14
+ #define USEARCH_VERSION_PATCH 2
15
15
 
16
16
  // Inferring C++ version
17
17
  // https://stackoverflow.com/a/61552074
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.8.1",
3
+ "version": "2.8.2",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
@@ -61,20 +61,29 @@ jobs:
61
61
 
62
62
  test_javascript:
63
63
  name: Test JavaScript
64
- runs-on: ubuntu-latest
65
- strategy:
66
- matrix:
67
- os: [ubuntu-22.04, macOS-11, windows-2022]
68
- node-version: [18.x]
64
+ runs-on: ubuntu-22.04
65
+ env:
66
+ CC: gcc-12
67
+ CXX: g++-12
68
+
69
69
  steps:
70
70
  - uses: actions/checkout@v4
71
+ with:
72
+ ref: "main"
73
+ - run: git submodule update --init --recursive
74
+
75
+ - name: Update compilers
76
+ run: |
77
+ sudo apt update
78
+ sudo apt install -y cmake build-essential libjemalloc-dev libomp-dev gcc-12 g++-12
79
+
71
80
  - name: Set up Node.js
72
81
  uses: actions/setup-node@v3
73
82
  with:
74
- node-version: "18.x"
83
+ node-version: 18
75
84
 
76
- - name: Build locally
77
- run: npm install
78
-
79
- - name: Test
80
- run: npm test
85
+ - name: Build and Test
86
+ run: |
87
+ npm install
88
+ npm ci
89
+ npm test
@@ -18,18 +18,18 @@ permissions:
18
18
  jobs:
19
19
  versioning:
20
20
  name: Semantic Release
21
- runs-on: ubuntu-latest
21
+ runs-on: ubuntu-22.04
22
22
  steps:
23
23
  - uses: actions/checkout@v3
24
24
  with:
25
25
  persist-credentials: false
26
26
  - uses: actions/setup-node@v3
27
- - run: npm install --save-dev @semantic-release/exec @semantic-release/git conventional-changelog-eslint semantic-release && npx semantic-release
27
+ - run: npm install --save-dev --prefix ./package-ci @semantic-release/exec @semantic-release/git conventional-changelog-eslint semantic-release && npx --prefix ./package-ci semantic-release
28
28
 
29
29
  rebase:
30
30
  name: Rebase Dev. Branch
31
31
  needs: versioning
32
- runs-on: ubuntu-latest
32
+ runs-on: ubuntu-22.04
33
33
  steps:
34
34
  - name: Checkout the latest code
35
35
  uses: actions/checkout@v3
@@ -109,17 +109,33 @@ jobs:
109
109
  name: Publish JavaScript
110
110
  needs: versioning
111
111
  runs-on: ubuntu-22.04
112
+ env:
113
+ CC: gcc-12
114
+ CXX: g++-12
115
+
112
116
  steps:
113
117
  - uses: actions/checkout@v3
114
118
  with:
115
119
  ref: "main"
116
120
  - run: git submodule update --init --recursive
117
- - uses: actions/setup-node@v3
121
+
122
+ - name: Update compilers
123
+ run: |
124
+ sudo apt update
125
+ sudo apt install -y cmake build-essential libjemalloc-dev libomp-dev gcc-12 g++-12
126
+
127
+ - name: Set up Node.js
128
+ uses: actions/setup-node@v3
118
129
  with:
119
130
  node-version: 18
120
- - run: npm install
121
- - run: npm ci
122
- - run: npm test
123
- - uses: JS-DevTools/npm-publish@v2
131
+
132
+ - name: Build and Test
133
+ run: |
134
+ npm install
135
+ npm ci
136
+ npm test
137
+
138
+ - name: Publish
139
+ uses: JS-DevTools/npm-publish@v2
124
140
  with:
125
141
  token: ${{ secrets.NPM_TOKEN }}
@@ -46,9 +46,9 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
46
46
  if(NOT APPLE)
47
47
  add_compile_options(-march=native)
48
48
  endif()
49
- add_compile_options(-pedantic -ferror-limit=1 -ffast-math)
49
+ add_compile_options(-pedantic -ferror-limit=1)
50
50
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
51
- add_compile_options(-march=native -pedantic -fmax-errors=1 -ffast-math)
51
+ add_compile_options(-march=native -pedantic -fmax-errors=1)
52
52
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
53
53
  add_compile_options(-w -ferror-limit=1)
54
54
  endif()
package/simsimd/README.md CHANGED
@@ -34,7 +34,7 @@ Given 1000 embeddings from OpenAI Ada API with 1536 dimensions, running on the A
34
34
 
35
35
  ### Intel Sapphire Rapids
36
36
 
37
- On the Intel Sapphire Rapids platform, SimSIMD was benchmarked against auto-vectorized code using GCC 12. GCC handles single-precision `float` and `int8_t` well. However, it fails on `_Float16` arrays, which has been part of the C language since 2011.
37
+ On the Intel Sapphire Rapids platform, SimSIMD was benchmarked against auto-vectorized code using GCC 12. GCC handles single-precision `float`, but might not be the best choice for `int8` and `_Float16` arrays, which has been part of the C language since 2011.
38
38
 
39
39
  | | GCC 12 `f32` | GCC 12 `f16` | SimSIMD `f16` | `f16` improvement |
40
40
  | :-------------- | -----------: | -----------: | ------------: | ----------------: |
@@ -3,8 +3,8 @@
3
3
 
4
4
  #include <benchmark/benchmark.h>
5
5
 
6
- #define SIMSIMD_RSQRT sqrtf
7
- #define SIMSIMD_LOG logf
6
+ #define SIMSIMD_RSQRT(x) (1 / sqrtf(x))
7
+ #define SIMSIMD_LOG(x) (logf(x))
8
8
  #include <simsimd/simsimd.h>
9
9
 
10
10
  namespace bm = benchmark;
@@ -78,7 +78,7 @@ void register_(std::string name, metric_at* distance_func, metric_at* baseline_f
78
78
  using pair_bytes_t = vectors_pair_gt<scalar_at, 1536 / sizeof(scalar_at)>;
79
79
 
80
80
  std::size_t seconds = 10;
81
- std::size_t threads = std::thread::hardware_concurrency(); // 1;
81
+ std::size_t threads = 1; // std::thread::hardware_concurrency(); // 1;
82
82
  std::string name_dims = name + "_" + std::to_string(pair_dims_t{}.dimensions()) + "d";
83
83
  std::string name_bytes = name + "_" + std::to_string(pair_bytes_t{}.size_bytes()) + "b";
84
84
 
@@ -95,7 +95,8 @@ int main(int argc, char** argv) {
95
95
  bool compiled_with_sve = false;
96
96
  bool compiled_with_neon = false;
97
97
  bool compiled_with_avx2 = false;
98
- bool compiled_with_avx512popcnt = false;
98
+ bool compiled_with_avx512vpopcntdq = false;
99
+ bool compiled_with_avx512vnni = false;
99
100
 
100
101
  #if defined(__ARM_FEATURE_SVE)
101
102
  compiled_with_sve = true;
@@ -107,7 +108,10 @@ int main(int argc, char** argv) {
107
108
  compiled_with_avx2 = true;
108
109
  #endif
109
110
  #if defined(__AVX512VPOPCNTDQ__)
110
- compiled_with_avx512popcnt = true;
111
+ compiled_with_avx512vpopcntdq = true;
112
+ #endif
113
+ #if defined(__AVX512VNNI__)
114
+ compiled_with_avx512vnni = true;
111
115
  #endif
112
116
 
113
117
  // Log supported functionality
@@ -117,7 +121,8 @@ int main(int argc, char** argv) {
117
121
  std::printf("- Arm NEON support enabled: %s\n", flags[compiled_with_neon]);
118
122
  std::printf("- Arm SVE support enabled: %s\n", flags[compiled_with_sve]);
119
123
  std::printf("- x86 AVX2 support enabled: %s\n", flags[compiled_with_avx2]);
120
- std::printf("- x86 AVX512VPOPCNTDQ support enabled: %s\n", flags[compiled_with_avx512popcnt]);
124
+ std::printf("- x86 AVX512VPOPCNTDQ support enabled: %s\n", flags[compiled_with_avx512vpopcntdq]);
125
+ std::printf("- x86 AVX512VNNI support enabled: %s\n", flags[compiled_with_avx512vnni]);
121
126
  std::printf("\n");
122
127
 
123
128
  // Run the benchmarks
@@ -133,41 +133,55 @@ simsimd_sve_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_siz
133
133
 
134
134
  #if SIMSIMD_TARGET_X86_AVX512
135
135
 
136
- __attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f"))) //
136
+ __attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f,bmi2"))) //
137
137
  inline static simsimd_f32_t
138
138
  simsimd_avx512_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
139
139
  __m512i differences_vec = _mm512_setzero_si512();
140
- for (simsimd_size_t i = 0; i < n_words; i += 64) {
141
-
142
- // Compute mask for tail elements
143
- __mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
144
- __m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
145
- __m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
146
- __m512i xor_vec = _mm512_xor_si512(a_vec, b_vec);
147
-
148
- differences_vec = _mm512_add_epi64(differences_vec, _mm512_popcnt_epi64(xor_vec));
140
+ __m512i a_vec, b_vec;
141
+
142
+ simsimd_avx512_b8_hamming_cycle:
143
+ if (n_words < 64) {
144
+ __mmask64 mask = _bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words);
145
+ a_vec = _mm512_maskz_loadu_epi8(mask, a);
146
+ b_vec = _mm512_maskz_loadu_epi8(mask, b);
147
+ n_words = 0;
148
+ } else {
149
+ a_vec = _mm512_loadu_epi8(a);
150
+ b_vec = _mm512_loadu_epi8(b);
151
+ a += 64, b += 64, n_words -= 64;
149
152
  }
153
+ __m512i xor_vec = _mm512_xor_si512(a_vec, b_vec);
154
+ differences_vec = _mm512_add_epi64(differences_vec, _mm512_popcnt_epi64(xor_vec));
155
+ if (n_words)
156
+ goto simsimd_avx512_b8_hamming_cycle;
150
157
 
151
158
  simsimd_size_t differences = _mm512_reduce_add_epi64(differences_vec);
152
159
  return (simsimd_f32_t)differences;
153
160
  }
154
161
 
155
- __attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f"))) //
162
+ __attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f,bmi2"))) //
156
163
  inline static simsimd_f32_t
157
164
  simsimd_avx512_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
158
165
  __m512i intersection_vec = _mm512_setzero_si512(), union_vec = _mm512_setzero_si512();
159
- for (simsimd_size_t i = 0; i < n_words; i += 64) {
160
-
161
- // Compute mask for tail elements
162
- __mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
163
- __m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
164
- __m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
165
- __m512i and_vec = _mm512_and_si512(a_vec, b_vec);
166
- __m512i or_vec = _mm512_or_si512(a_vec, b_vec);
167
-
168
- intersection_vec = _mm512_add_epi64(intersection_vec, _mm512_popcnt_epi64(and_vec));
169
- union_vec = _mm512_add_epi64(union_vec, _mm512_popcnt_epi64(or_vec));
166
+ __m512i a_vec, b_vec;
167
+
168
+ simsimd_avx512_b8_jaccard_cycle:
169
+ if (n_words < 64) {
170
+ __mmask64 mask = _bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words);
171
+ a_vec = _mm512_maskz_loadu_epi8(mask, a);
172
+ b_vec = _mm512_maskz_loadu_epi8(mask, b);
173
+ n_words = 0;
174
+ } else {
175
+ a_vec = _mm512_loadu_epi8(a);
176
+ b_vec = _mm512_loadu_epi8(b);
177
+ a += 64, b += 64, n_words -= 64;
170
178
  }
179
+ __m512i and_vec = _mm512_and_si512(a_vec, b_vec);
180
+ __m512i or_vec = _mm512_or_si512(a_vec, b_vec);
181
+ intersection_vec = _mm512_add_epi64(intersection_vec, _mm512_popcnt_epi64(and_vec));
182
+ union_vec = _mm512_add_epi64(union_vec, _mm512_popcnt_epi64(or_vec));
183
+ if (n_words)
184
+ goto simsimd_avx512_b8_jaccard_cycle;
171
185
 
172
186
  simsimd_size_t intersection = _mm512_reduce_add_epi64(intersection_vec),
173
187
  union_ = _mm512_reduce_add_epi64(union_vec);
@@ -370,58 +370,70 @@ simsimd_avx512_f32_log2(__m512 x) {
370
370
  return _mm512_add_ps(_mm512_mul_ps(p, _mm512_sub_ps(m, one)), e);
371
371
  }
372
372
 
373
- __attribute__((target("avx512f,avx512vl"))) //
373
+ __attribute__((target("avx512f,avx512vl,bmi2"))) //
374
374
  inline static simsimd_f32_t
375
375
  simsimd_avx512_f32_kl(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n) {
376
376
  __m512 sum_vec = _mm512_set1_ps(0);
377
377
  simsimd_f32_t epsilon = 1e-6;
378
378
  __m512 epsilon_vec = _mm512_set1_ps(epsilon);
379
- for (simsimd_size_t i = 0; i < n; i += 16) {
380
- __mmask16 mask = n - i >= 16 ? 0xFFFF : ((1u << (n - i)) - 1u);
381
- __m512 a_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, a + i), epsilon_vec);
382
- __m512 b_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, b + i), epsilon_vec);
383
- __m512 ratio_vec = _mm512_div_ps(a_vec, b_vec);
384
- __m512 log_ratio_vec = simsimd_avx512_f32_log2(ratio_vec);
385
- __m512 prod_vec = _mm512_mul_ps(a_vec, log_ratio_vec);
386
- sum_vec = _mm512_maskz_add_ps(mask, sum_vec, prod_vec);
379
+ __m512 a_vec, b_vec;
380
+
381
+ simsimd_avx512_f32_kl_cycle:
382
+ if (n < 16) {
383
+ __mmask16 mask = _bzhi_u32(0xFFFFFFFF, n);
384
+ a_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, a), epsilon_vec);
385
+ b_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, b), epsilon_vec);
386
+ n = 0;
387
+ } else {
388
+ a_vec = _mm512_add_ps(_mm512_loadu_ps(a), epsilon_vec);
389
+ b_vec = _mm512_add_ps(_mm512_loadu_ps(b), epsilon_vec);
390
+ a += 16, b += 16, n -= 16;
387
391
  }
392
+ __m512 ratio_vec = _mm512_div_ps(a_vec, b_vec);
393
+ __m512 log_ratio_vec = simsimd_avx512_f32_log2(ratio_vec);
394
+ __m512 prod_vec = _mm512_mul_ps(a_vec, log_ratio_vec);
395
+ sum_vec = _mm512_add_ps(sum_vec, prod_vec);
396
+ if (n)
397
+ goto simsimd_avx512_f32_kl_cycle;
398
+
388
399
  simsimd_f32_t log2_normalizer = 0.693147181f;
389
400
  return _mm512_reduce_add_ps(sum_vec) * log2_normalizer;
390
401
  }
391
402
 
392
- __attribute__((target("avx512f,avx512vl"))) //
403
+ __attribute__((target("avx512f,avx512vl,bmi2"))) //
393
404
  inline static simsimd_f32_t
394
405
  simsimd_avx512_f32_js(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n) {
395
406
  __m512 sum_a_vec = _mm512_set1_ps(0);
396
407
  __m512 sum_b_vec = _mm512_set1_ps(0);
397
408
  simsimd_f32_t epsilon = 1e-6;
398
409
  __m512 epsilon_vec = _mm512_set1_ps(epsilon);
399
- for (simsimd_size_t i = 0; i < n; i += 16) {
400
- __mmask16 mask = n - i >= 16 ? 0xFFFF : ((1u << (n - i)) - 1u);
401
- __m512 a_vec = _mm512_maskz_loadu_ps(mask, a + i);
402
- __m512 b_vec = _mm512_maskz_loadu_ps(mask, b + i);
403
- __m512 m_vec = _mm512_mul_ps(_mm512_add_ps(a_vec, b_vec), _mm512_set1_ps(0.5f)); // M = (P + Q) / 2
404
-
405
- // Avoid division by zero problems from probabilities under zero down the road.
406
- // Masking is a nicer way to do this, than adding the `epsilon` to every component.
407
- __mmask16 nonzero_mask_a = _mm512_cmp_ps_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
408
- __mmask16 nonzero_mask_b = _mm512_cmp_ps_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
409
- __mmask16 nonzero_mask = nonzero_mask_a & nonzero_mask_b & mask;
410
-
411
- // Division is an expensive operation. Instead of doing it twice,
412
- // we can approximate the reciprocal of `m` and multiply instead.
413
- __m512 m_recip_approx = _mm512_rcp14_ps(m_vec); // or `_mm512_rcp28_ps` for slightly better precision
414
- __m512 ratio_a_vec = _mm512_mul_ps(a_vec, m_recip_approx);
415
- __m512 ratio_b_vec = _mm512_mul_ps(b_vec, m_recip_approx);
416
-
417
- // The natural logarithm is equivalent to `log2`, multiplied by the `loge(2)`
418
- __m512 log_ratio_a_vec = simsimd_avx512_f32_log2(ratio_a_vec);
419
- __m512 log_ratio_b_vec = simsimd_avx512_f32_log2(ratio_b_vec);
420
-
421
- // Instead of separate multiplication and addition, invoke the FMA
422
- sum_a_vec = _mm512_maskz_fmadd_ps(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
423
- sum_b_vec = _mm512_maskz_fmadd_ps(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
410
+ __m512 a_vec, b_vec;
411
+
412
+ simsimd_avx512_f32_js_cycle:
413
+ if (n < 16) {
414
+ __mmask16 mask = _bzhi_u32(0xFFFFFFFF, n);
415
+ a_vec = _mm512_maskz_loadu_ps(mask, a);
416
+ b_vec = _mm512_maskz_loadu_ps(mask, b);
417
+ n = 0;
418
+ } else {
419
+ a_vec = _mm512_loadu_ps(a);
420
+ b_vec = _mm512_loadu_ps(b);
421
+ a += 16, b += 16, n -= 16;
424
422
  }
423
+ __m512 m_vec = _mm512_mul_ps(_mm512_add_ps(a_vec, b_vec), _mm512_set1_ps(0.5f));
424
+ __mmask16 nonzero_mask_a = _mm512_cmp_ps_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
425
+ __mmask16 nonzero_mask_b = _mm512_cmp_ps_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
426
+ __mmask16 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
427
+ __m512 m_recip_approx = _mm512_rcp14_ps(m_vec);
428
+ __m512 ratio_a_vec = _mm512_mul_ps(a_vec, m_recip_approx);
429
+ __m512 ratio_b_vec = _mm512_mul_ps(b_vec, m_recip_approx);
430
+ __m512 log_ratio_a_vec = simsimd_avx512_f32_log2(ratio_a_vec);
431
+ __m512 log_ratio_b_vec = simsimd_avx512_f32_log2(ratio_b_vec);
432
+ sum_a_vec = _mm512_maskz_fmadd_ps(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
433
+ sum_b_vec = _mm512_maskz_fmadd_ps(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
434
+ if (n)
435
+ goto simsimd_avx512_f32_js_cycle;
436
+
425
437
  simsimd_f32_t log2_normalizer = 0.693147181f;
426
438
  return _mm512_reduce_add_ps(_mm512_add_ps(sum_a_vec, sum_b_vec)) * 0.5f * log2_normalizer;
427
439
  }
@@ -456,56 +468,68 @@ simsimd_avx512_f16_log2(__m512h x) {
456
468
  return _mm512_add_ph(_mm512_mul_ph(p, _mm512_sub_ph(m, one)), e);
457
469
  }
458
470
 
459
- __attribute__((target("avx512f,avx512vl,avx512fp16"))) //
471
+ __attribute__((target("avx512f,avx512vl,avx512fp16,bmi2"))) //
460
472
  inline static simsimd_f32_t
461
473
  simsimd_avx512_f16_kl(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
462
474
  __m512h sum_vec = _mm512_set1_ph((_Float16)0);
463
475
  __m512h epsilon_vec = _mm512_set1_ph((_Float16)1e-6f);
464
- for (simsimd_size_t i = 0; i < n; i += 32) {
465
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
466
- __m512h a_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a + i)), epsilon_vec);
467
- __m512h b_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b + i)), epsilon_vec);
468
- __m512h ratio_vec = _mm512_div_ph(a_vec, b_vec);
469
- __m512h log_ratio_vec = simsimd_avx512_f16_log2(ratio_vec);
470
- __m512h prod_vec = _mm512_mul_ph(a_vec, log_ratio_vec);
471
- sum_vec = _mm512_maskz_add_ph(mask, sum_vec, prod_vec);
476
+ __m512h a_vec, b_vec;
477
+
478
+ simsimd_avx512_f16_kl_cycle:
479
+ if (n < 32) {
480
+ __mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
481
+ a_vec = _mm512_maskz_add_ph(mask, _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a)), epsilon_vec);
482
+ b_vec = _mm512_maskz_add_ph(mask, _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b)), epsilon_vec);
483
+ n = 0;
484
+ } else {
485
+ a_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_loadu_epi16(a)), epsilon_vec);
486
+ b_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_loadu_epi16(b)), epsilon_vec);
487
+ a += 32, b += 32, n -= 32;
472
488
  }
489
+ __m512h ratio_vec = _mm512_div_ph(a_vec, b_vec);
490
+ __m512h log_ratio_vec = simsimd_avx512_f16_log2(ratio_vec);
491
+ __m512h prod_vec = _mm512_mul_ph(a_vec, log_ratio_vec);
492
+ sum_vec = _mm512_add_ph(sum_vec, prod_vec);
493
+ if (n)
494
+ goto simsimd_avx512_f16_kl_cycle;
495
+
473
496
  simsimd_f32_t log2_normalizer = 0.693147181f;
474
497
  return _mm512_reduce_add_ph(sum_vec) * log2_normalizer;
475
498
  }
476
499
 
477
- __attribute__((target("avx512f,avx512vl,avx512fp16"))) //
500
+ __attribute__((target("avx512f,avx512vl,avx512fp16,bmi2"))) //
478
501
  inline static simsimd_f32_t
479
502
  simsimd_avx512_f16_js(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
480
503
  __m512h sum_a_vec = _mm512_set1_ph((_Float16)0);
481
504
  __m512h sum_b_vec = _mm512_set1_ph((_Float16)0);
482
505
  __m512h epsilon_vec = _mm512_set1_ph((_Float16)1e-6f);
483
- for (simsimd_size_t i = 0; i < n; i += 32) {
484
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
485
- __m512h a_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a + i));
486
- __m512h b_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b + i));
487
- __m512h m_vec = _mm512_mul_ph(_mm512_add_ph(a_vec, b_vec), _mm512_set1_ph((_Float16)0.5f)); // M = (P + Q) / 2
488
-
489
- // Avoid division by zero problems from probabilities under zero down the road.
490
- // Masking is a nicer way to do this, than adding the `epsilon` to every component.
491
- __mmask32 nonzero_mask_a = _mm512_cmp_ph_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
492
- __mmask32 nonzero_mask_b = _mm512_cmp_ph_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
493
- __mmask32 nonzero_mask = nonzero_mask_a & nonzero_mask_b & mask;
494
-
495
- // Division is an expensive operation. Instead of doing it twice,
496
- // we can approximate the reciprocal of `m` and multiply instead.
497
- __m512h m_recip_approx = _mm512_rcp_ph(m_vec);
498
- __m512h ratio_a_vec = _mm512_mul_ph(a_vec, m_recip_approx);
499
- __m512h ratio_b_vec = _mm512_mul_ph(b_vec, m_recip_approx);
500
-
501
- // The natural logarithm is equivalent to `log2`, multiplied by the `loge(2)`
502
- __m512h log_ratio_a_vec = simsimd_avx512_f16_log2(ratio_a_vec);
503
- __m512h log_ratio_b_vec = simsimd_avx512_f16_log2(ratio_b_vec);
504
-
505
- // Instead of separate multiplication and addition, invoke the FMA
506
- sum_a_vec = _mm512_maskz_fmadd_ph(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
507
- sum_b_vec = _mm512_maskz_fmadd_ph(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
506
+ __m512h a_vec, b_vec;
507
+
508
+ simsimd_avx512_f16_js_cycle:
509
+ if (n < 32) {
510
+ __mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
511
+ a_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a));
512
+ b_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b));
513
+ n = 0;
514
+ } else {
515
+ a_vec = _mm512_castsi512_ph(_mm512_loadu_epi16(a));
516
+ b_vec = _mm512_castsi512_ph(_mm512_loadu_epi16(b));
517
+ a += 32, b += 32, n -= 32;
508
518
  }
519
+ __m512h m_vec = _mm512_mul_ph(_mm512_add_ph(a_vec, b_vec), _mm512_set1_ph((_Float16)0.5f));
520
+ __mmask32 nonzero_mask_a = _mm512_cmp_ph_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
521
+ __mmask32 nonzero_mask_b = _mm512_cmp_ph_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
522
+ __mmask32 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
523
+ __m512h m_recip_approx = _mm512_rcp_ph(m_vec);
524
+ __m512h ratio_a_vec = _mm512_mul_ph(a_vec, m_recip_approx);
525
+ __m512h ratio_b_vec = _mm512_mul_ph(b_vec, m_recip_approx);
526
+ __m512h log_ratio_a_vec = simsimd_avx512_f16_log2(ratio_a_vec);
527
+ __m512h log_ratio_b_vec = simsimd_avx512_f16_log2(ratio_b_vec);
528
+ sum_a_vec = _mm512_maskz_fmadd_ph(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
529
+ sum_b_vec = _mm512_maskz_fmadd_ph(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
530
+ if (n)
531
+ goto simsimd_avx512_f16_js_cycle;
532
+
509
533
  simsimd_f32_t log2_normalizer = 0.693147181f;
510
534
  return _mm512_reduce_add_ph(_mm512_add_ph(sum_a_vec, sum_b_vec)) * 0.5f * log2_normalizer;
511
535
  }
@@ -74,6 +74,7 @@ typedef enum {
74
74
  simsimd_cap_x86_avx2fp16_k = 1 << 22, ///< x86 AVX2 with FP16 capability
75
75
  simsimd_cap_x86_avx512fp16_k = 1 << 23, ///< x86 AVX512 with FP16 capability
76
76
  simsimd_cap_x86_avx512vpopcntdq_k = 1 << 24, ///< x86 AVX512 VPOPCNTDQ instruction capability
77
+ simsimd_cap_x86_avx512vnni_k = 1 << 25, ///< x86 AVX512 VNNI instruction capability
77
78
 
78
79
  } simsimd_capability_t;
79
80
 
@@ -112,42 +113,50 @@ inline static simsimd_capability_t simsimd_capabilities() {
112
113
  /// The states of 4 registers populated for a specific "cpuid" assmebly call
113
114
  union four_registers_t {
114
115
  int array[4];
115
- struct {
116
+ struct separate_t {
116
117
  unsigned eax, ebx, ecx, edx;
117
- };
118
+ } named;
118
119
  } info1, info7;
119
120
 
120
121
  #ifdef _MSC_VER
121
122
  __cpuidex(info1.array, 1, 0);
122
123
  __cpuidex(info7.array, 7, 0);
123
124
  #else
124
- __asm__ __volatile__("cpuid" : "=a"(info1.eax), "=b"(info1.ebx), "=c"(info1.ecx), "=d"(info1.edx) : "a"(1), "c"(0));
125
- __asm__ __volatile__("cpuid" : "=a"(info7.eax), "=b"(info7.ebx), "=c"(info7.ecx), "=d"(info7.edx) : "a"(7), "c"(0));
125
+ __asm__ __volatile__("cpuid"
126
+ : "=a"(info1.named.eax), "=b"(info1.named.ebx), "=c"(info1.named.ecx), "=d"(info1.named.edx)
127
+ : "a"(1), "c"(0));
128
+ __asm__ __volatile__("cpuid"
129
+ : "=a"(info7.named.eax), "=b"(info7.named.ebx), "=c"(info7.named.ecx), "=d"(info7.named.edx)
130
+ : "a"(7), "c"(0));
126
131
  #endif
127
132
 
128
133
  // Check for AVX2 (Function ID 7, EBX register)
129
134
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L148
130
- unsigned supports_avx2 = (info7.ebx & 0x00000020) != 0;
135
+ unsigned supports_avx2 = (info7.named.ebx & 0x00000020) != 0;
131
136
  // Check for F16C (Function ID 1, ECX register)
132
137
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L107
133
- unsigned supports_f16c = (info1.ecx & 0x20000000) != 0;
138
+ unsigned supports_f16c = (info1.named.ecx & 0x20000000) != 0;
134
139
  // Check for AVX512F (Function ID 7, EBX register)
135
140
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L155
136
- unsigned supports_avx512f = (info7.ebx & 0x00010000) != 0;
141
+ unsigned supports_avx512f = (info7.named.ebx & 0x00010000) != 0;
137
142
  // Check for AVX512FP16 (Function ID 7, EDX register)
138
143
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L198C9-L198C23
139
- unsigned supports_avx512fp16 = (info7.edx & 0x00800000) != 0;
144
+ unsigned supports_avx512fp16 = (info7.named.edx & 0x00800000) != 0;
140
145
  // Check for VPOPCNTDQ (Function ID 1, ECX register)
141
146
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L182C30-L182C40
142
- unsigned supports_avx512vpopcntdq = (info1.ecx & 0x00004000) != 0;
143
-
144
- return (simsimd_capability_t)( //
145
- (simsimd_cap_x86_avx2_k * supports_avx2) | //
146
- (simsimd_cap_x86_avx512_k * supports_avx512f) | //
147
- (simsimd_cap_x86_avx2fp16_k * (supports_avx2 && supports_f16c)) | //
148
- (simsimd_cap_x86_avx512fp16_k * (supports_avx512fp16 && supports_avx512f)) | //
149
- (simsimd_cap_x86_avx512vpopcntdq_k * (supports_avx512vpopcntdq && supports_avx512vpopcntdq)) //
150
- | (simsimd_cap_serial_k));
147
+ unsigned supports_avx512vpopcntdq = (info1.named.ecx & 0x00004000) != 0;
148
+ // Check for VNNI (Function ID 1, ECX register)
149
+ // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L180
150
+ unsigned supports_avx512vnni = (info1.named.ecx & 0x00000800) != 0;
151
+
152
+ return (simsimd_capability_t)( //
153
+ (simsimd_cap_x86_avx2_k * supports_avx2) | //
154
+ (simsimd_cap_x86_avx512_k * supports_avx512f) | //
155
+ (simsimd_cap_x86_avx2fp16_k * (supports_avx2 && supports_f16c)) | //
156
+ (simsimd_cap_x86_avx512fp16_k * (supports_avx512fp16 && supports_avx512f)) | //
157
+ (simsimd_cap_x86_avx512vpopcntdq_k * (supports_avx512vpopcntdq)) | //
158
+ (simsimd_cap_x86_avx512vnni_k * (supports_avx512vnni)) | //
159
+ (simsimd_cap_serial_k));
151
160
 
152
161
  #endif // SIMSIMD_TARGET_X86
153
162
 
@@ -319,11 +328,11 @@ inline static void simsimd_find_metric_punned( //
319
328
  }
320
329
  #endif
321
330
  #if SIMSIMD_TARGET_X86_AVX512
322
- if (viable & simsimd_cap_x86_avx512_k)
331
+ if (viable & simsimd_cap_x86_avx512vnni_k)
323
332
  switch (kind) {
324
- case simsimd_metric_ip_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_ip, *c = simsimd_cap_x86_avx512_k; return;
325
- case simsimd_metric_cos_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_cos, *c = simsimd_cap_x86_avx512_k; return;
326
- case simsimd_metric_l2sq_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_l2sq, *c = simsimd_cap_x86_avx512_k; return;
333
+ case simsimd_metric_ip_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_ip, *c = simsimd_cap_x86_avx512vnni_k; return;
334
+ case simsimd_metric_cos_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_cos, *c = simsimd_cap_x86_avx512vnni_k; return;
335
+ case simsimd_metric_l2sq_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_l2sq, *c = simsimd_cap_x86_avx512vnni_k; return;
327
336
  default: break;
328
337
  }
329
338
  #endif