usearch 2.17.1 → 2.19.4

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.
Files changed (47) hide show
  1. package/README.md +6 -2
  2. package/c/lib.cpp +5 -0
  3. package/c/module.modulemap +4 -0
  4. package/c/usearch.h +12 -4
  5. package/cpp/README.md +1 -1
  6. package/cpp/bench.cpp +1 -1
  7. package/cpp/test.cpp +110 -16
  8. package/include/usearch/index.hpp +48 -35
  9. package/include/usearch/index_dense.hpp +34 -19
  10. package/include/usearch/index_plugins.hpp +29 -7
  11. package/javascript/README.md +8 -6
  12. package/javascript/dist/cjs/usearch.d.ts +9 -4
  13. package/javascript/dist/cjs/usearch.js +23 -8
  14. package/javascript/dist/esm/usearch.d.ts +9 -4
  15. package/javascript/dist/esm/usearch.js +21 -7
  16. package/javascript/lib.cpp +137 -59
  17. package/javascript/usearch.test.js +359 -192
  18. package/javascript/usearch.ts +35 -11
  19. package/package.json +1 -1
  20. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  21. package/prebuilds/linux-arm64/usearch.node +0 -0
  22. package/prebuilds/linux-x64/usearch.node +0 -0
  23. package/simsimd/CONTRIBUTING.md +23 -8
  24. package/simsimd/Cargo.lock +1 -1
  25. package/simsimd/Cargo.toml +1 -1
  26. package/simsimd/README.md +49 -9
  27. package/simsimd/build.rs +7 -3
  28. package/simsimd/c/lib.c +6 -1
  29. package/simsimd/include/simsimd/binary.h +3 -3
  30. package/simsimd/include/simsimd/dot.h +2 -2
  31. package/simsimd/include/simsimd/elementwise.h +26 -17
  32. package/simsimd/include/simsimd/simsimd.h +125 -49
  33. package/simsimd/include/simsimd/sparse.h +30 -29
  34. package/simsimd/include/simsimd/spatial.h +4 -3
  35. package/simsimd/include/simsimd/types.h +19 -8
  36. package/simsimd/javascript/lib.c +0 -1
  37. package/simsimd/package-lock.json +411 -362
  38. package/simsimd/package.json +9 -9
  39. package/simsimd/rust/lib.rs +63 -29
  40. package/simsimd/scripts/bench.cxx +13 -0
  41. package/simsimd/scripts/bench_cosine.rs +8 -0
  42. package/simsimd/scripts/bench_native.rs +4 -0
  43. package/simsimd/scripts/bench_sqeuclidean.rs +8 -0
  44. package/simsimd/scripts/test.c +22 -0
  45. package/simsimd/scripts/test.py +80 -4
  46. package/simsimd/swift/SimSIMD.swift +17 -0
  47. package/simsimd/swift/Test.swift +36 -0
@@ -16,8 +16,8 @@ type CompiledSearchResult = [
16
16
  ];
17
17
 
18
18
  interface CompiledIndex {
19
- add(keys: BigUint64Array, vectors: Vector): void;
20
- search(vectors: VectorOrMatrix, k: number): CompiledSearchResult;
19
+ add(keys: BigUint64Array, vectors: Vector, threads: number): void;
20
+ search(vectors: VectorOrMatrix, k: number, threads: number): CompiledSearchResult;
21
21
  contains(keys: BigUint64Array): boolean[];
22
22
  count(keys: BigUint64Array): number | number[];
23
23
  remove(keys: BigUint64Array): number[];
@@ -37,7 +37,8 @@ interface Compiled {
37
37
  queries: VectorOrMatrix,
38
38
  dimensions: number,
39
39
  count: number,
40
- metric: MetricKind
40
+ metric: MetricKind,
41
+ threads: number
41
42
  ): CompiledSearchResult;
42
43
  }
43
44
 
@@ -326,10 +327,12 @@ export class Index {
326
327
  * If a single key is provided, it is associated with all provided vectors.
327
328
  * @param {Float32Array|Float64Array|Int8Array} vectors - Input matrix representing vectors,
328
329
  * matrix of size n * d, where n is the number of vectors, and d is their dimensionality.
330
+ * @param {number} [threads=0] - Optional, default is 0. Number of threads to use for indexing.
331
+ * If set to 0, the number of threads is determined automatically.
329
332
  * @throws Will throw an error if the length of keys doesn't match the number of vectors
330
333
  * or if it's not a single key.
331
334
  */
332
- add(keys: bigint | bigint[] | BigUint64Array, vectors: Vector) {
335
+ add(keys: bigint | bigint[] | BigUint64Array, vectors: Vector, threads: number = 0): void {
333
336
  let normalizedKeys = normalizeKeys(keys);
334
337
  let normalizedVectors = normalizeVectors(
335
338
  vectors,
@@ -351,8 +354,14 @@ export class Index {
351
354
  );
352
355
  }
353
356
 
357
+ if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
358
+ throw new Error(
359
+ "`threads` must be a non-negative integer representing the number of threads to use for searching."
360
+ );
361
+ }
362
+
354
363
  // Call the compiled method
355
- this.#compiledIndex.add(normalizedKeys, normalizedVectors);
364
+ this.#compiledIndex.add(normalizedKeys, normalizedVectors, threads);
356
365
  }
357
366
 
358
367
  /**
@@ -372,16 +381,22 @@ export class Index {
372
381
  *
373
382
  * @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of TypedArray.
374
383
  * @param {number} k - The number of nearest neighbors to search for each query vector.
384
+ * @param {number} [threads=0] - Optional, default is 0. Number of threads to use for searching. If set to 0, the number of threads is determined automatically.
375
385
  * @return {Matches|BatchMatches} - Search results for one or more queries, containing keys, distances, and counts of the matches found.
376
386
  * @throws Will throw an error if `k` is not a positive integer or if the size of the vectors is not a multiple of dimensions.
377
387
  * @throws Will throw an error if `vectors` is not a valid input type (TypedArray or an array of TypedArray) or if its flattened size is not a multiple of dimensions.
378
388
  */
379
- search(vectors: VectorOrMatrix, k: number): Matches | BatchMatches {
389
+ search(vectors: VectorOrMatrix, k: number, threads: number = 0): Matches | BatchMatches {
380
390
  if ((!Number.isNaN(k) && typeof k !== "number") || k <= 0) {
381
391
  throw new Error(
382
392
  "`k` must be a positive integer representing the number of nearest neighbors to search for."
383
393
  );
384
394
  }
395
+ if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
396
+ throw new Error(
397
+ "`threads` must be a non-negative integer representing the number of threads to use for searching."
398
+ );
399
+ }
385
400
 
386
401
  const normalizedVectors = normalizeVectors(
387
402
  vectors,
@@ -389,7 +404,7 @@ export class Index {
389
404
  );
390
405
 
391
406
  // Call the compiled method and create Matches or BatchMatches object with the result
392
- const result = this.#compiledIndex.search(normalizedVectors, k);
407
+ const result = this.#compiledIndex.search(normalizedVectors, k, threads);
393
408
  const countInQueries =
394
409
  normalizedVectors.length / Number(this.#compiledIndex.dimensions());
395
410
  const batchMatches = new BatchMatches(...result, k);
@@ -533,6 +548,7 @@ type NumberArrayConstructor =
533
548
  * @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
534
549
  * @param {number} count - The number of nearest neighbors to return for each query. If the dataset contains fewer vectors than the specified count, the result will contain only the available vectors.
535
550
  * @param {MetricKind} metric - The distance metric to be used for the search.
551
+ * @param {number} [threads=0] - Optional, default is 0. The number of threads to use for the search. If set to 0, the number of threads is determined automatically.
536
552
  * @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
537
553
  * @throws Will throw an error if `dimensions` and `count` are not positive integers.
538
554
  * @throws Will throw an error if `metric` is not a valid MetricKind.
@@ -546,20 +562,22 @@ type NumberArrayConstructor =
546
562
  * const dimensions = 2; // The number of elements in each vector.
547
563
  * const count = 1; // The number of nearest neighbors to return for each query.
548
564
  * const metric = MetricKind.IP; // Using the Inner Product distance metric.
565
+ * const threads = 0; // How many threads to use to perform the search.
549
566
  *
550
- * const result = exactSearch(dataset, queries, dimensions, count, metric);
567
+ * const result = exactSearch(dataset, queries, dimensions, count, metric, threads);
551
568
  * // result might be:
552
569
  * // {
553
570
  * // keys: BigUint64Array [ 1n ],
554
571
  * // distances: Float32Array [ some_value ],
555
572
  * // }
556
573
  */
557
- function exactSearch(
574
+ export function exactSearch(
558
575
  dataset: VectorOrMatrix,
559
576
  queries: VectorOrMatrix,
560
577
  dimensions: number,
561
578
  count: number,
562
- metric: MetricKind
579
+ metric: MetricKind,
580
+ threads: number = 0
563
581
  ): Matches | BatchMatches {
564
582
  // Validate and normalize the dimensions and count
565
583
  dimensions = Number(dimensions);
@@ -567,6 +585,11 @@ function exactSearch(
567
585
  if (count <= 0 || dimensions <= 0) {
568
586
  throw new Error("Dimensions and count must be positive integers.");
569
587
  }
588
+ if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
589
+ throw new Error(
590
+ "`threads` must be a non-negative integer representing the number of threads to use for searching."
591
+ );
592
+ }
570
593
 
571
594
  // Validate metric
572
595
  if (!Object.values(MetricKind).includes(metric)) {
@@ -599,7 +622,8 @@ function exactSearch(
599
622
  queries,
600
623
  dimensions,
601
624
  count,
602
- metric
625
+ metric,
626
+ threads
603
627
  );
604
628
 
605
629
  // Create and return a Matches or BatchMatches object with the result
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.17.1",
3
+ "version": "2.19.4",
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",
Binary file
Binary file
@@ -68,6 +68,7 @@ Replacing the default compiler across the entire system is not recommended on Ma
68
68
 
69
69
  ```sh
70
70
  brew install llvm openblas
71
+ unset DEVELOPER_DIR
71
72
  cmake -D CMAKE_BUILD_TYPE=Release \
72
73
  -D SIMSIMD_BUILD_TESTS=1 \
73
74
  -D SIMSIMD_BUILD_BENCHMARKS=1 \
@@ -86,23 +87,37 @@ cmake --build build_release --config Release
86
87
  When benchmarking, make sure to disable multi-threading in the BLAS library, as it may interfere with the results:
87
88
 
88
89
  ```sh
89
- export OPENBLAS_NUM_THREADS=1 # for OpenBLAS
90
- export MKL_NUM_THREADS=1 # for Intel MKL
91
- export VECLIB_MAXIMUM_THREADS=1 # for Apple Accelerate
92
- export BLIS_NUM_THREADS=1 # for BLIS
90
+ export OPENBLAS_NUM_THREADS=1 # for OpenBLAS
91
+ export MKL_NUM_THREADS=1 # for Intel MKL
92
+ export VECLIB_MAXIMUM_THREADS=1 # for Apple Accelerate
93
+ export BLIS_NUM_THREADS=1 # for BLIS
93
94
  ```
94
95
 
95
96
  ## Python
96
97
 
97
- Testing:
98
+ Python bindings are implemented using pure CPython, so you wouldn't need to install SWIG, PyBind11, or any other third-party library.
99
+ Still, you need a virtual environment.
100
+ If you already have one:
98
101
 
99
102
  ```sh
100
- pip install -e . # to install the package in editable mode
103
+ pip install -e . # build locally from source
101
104
  pip install pytest pytest-repeat tabulate # testing dependencies
102
105
  pytest scripts/test.py -s -x -Wd # to run tests
103
106
 
104
107
  # to check supported SIMD instructions:
105
- python -c "import simsimd; print(simsimd.get_capabilities())"
108
+ python -c "import simsimd; print(simsimd.get_capabilities())"
109
+ ```
110
+
111
+ Alternatively, use `uv` to create the virtual environment.
112
+
113
+ ```sh
114
+ uv venv --python 3.13t # or your preferred version
115
+ source .venv/bin/activate # activate the environment
116
+ uv pip install -e . # build locally from source
117
+
118
+ # to run GIL-related tests in a free-threaded environment:
119
+ uv pip install pytest pytest-repeat tabulate numpy scipy
120
+ PYTHON_GIL=0 python -m pytest scripts/test.py -s -x -Wd -k gil
106
121
  ```
107
122
 
108
123
  Here, `-s` will output the logs.
@@ -234,7 +249,7 @@ irm https://deno.land/install.ps1 | iex # Windows
234
249
  Testing:
235
250
 
236
251
  ```sh
237
- deno test --allow-read
252
+ deno test -A
238
253
  ```
239
254
 
240
255
  ### Bun
@@ -496,7 +496,7 @@ dependencies = [
496
496
 
497
497
  [[package]]
498
498
  name = "simsimd"
499
- version = "6.2.3"
499
+ version = "6.4.10"
500
500
  dependencies = [
501
501
  "cc",
502
502
  "criterion",
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "simsimd"
3
3
  description = "Portable mixed-precision BLAS-like vector math library for x86 and ARM"
4
- version = "6.2.3"
4
+ version = "6.4.10"
5
5
  edition = "2021"
6
6
  license = "Apache-2.0"
7
7
  authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
package/simsimd/README.md CHANGED
@@ -190,7 +190,7 @@ You can learn more about the technical implementation details in the following b
190
190
 
191
191
  <!-- Euclidean distance with different precision level -->
192
192
  <tr>
193
- <td colspan="4" align="center">eculidean distance between 1536d vectors in <code>int8</code></td>
193
+ <td colspan="4" align="center">euclidean distance between 1536d vectors in <code>int8</code></td>
194
194
  </tr>
195
195
  <tr>
196
196
  <td align="center"> <!-- scipy.spatial.distance.sqeuclidean -->
@@ -207,7 +207,7 @@ You can learn more about the technical implementation details in the following b
207
207
  </td>
208
208
  </tr>
209
209
  <tr>
210
- <td colspan="4" align="center">eculidean distance between 1536d vectors in <code>bfloat16</code></td>
210
+ <td colspan="4" align="center">euclidean distance between 1536d vectors in <code>bfloat16</code></td>
211
211
  </tr>
212
212
  <tr>
213
213
  <td align="center"> <!-- scipy.spatial.distance.sqeuclidean -->
@@ -223,7 +223,7 @@ You can learn more about the technical implementation details in the following b
223
223
  </td>
224
224
  </tr>
225
225
  <tr>
226
- <td colspan="4" align="center">eculidean distance between 1536d vectors in <code>float16</code></td>
226
+ <td colspan="4" align="center">euclidean distance between 1536d vectors in <code>float16</code></td>
227
227
  </tr>
228
228
  <tr>
229
229
  <td align="center"> <!-- scipy.spatial.distance.sqeuclidean -->
@@ -240,7 +240,7 @@ You can learn more about the technical implementation details in the following b
240
240
  </td>
241
241
  </tr>
242
242
  <tr>
243
- <td colspan="4" align="center">eculidean distance between 1536d vectors in <code>float32</code></td>
243
+ <td colspan="4" align="center">euclidean distance between 1536d vectors in <code>float32</code></td>
244
244
  </tr>
245
245
  <tr>
246
246
  <td align="center"> <!-- scipy.spatial.distance.sqeuclidean -->
@@ -257,7 +257,7 @@ You can learn more about the technical implementation details in the following b
257
257
  </td>
258
258
  </tr>
259
259
  <tr>
260
- <td colspan="4" align="center">eculidean distance between 1536d vectors in <code>float64</code></td>
260
+ <td colspan="4" align="center">euclidean distance between 1536d vectors in <code>float64</code></td>
261
261
  </tr>
262
262
  <tr>
263
263
  <td align="center"> <!-- scipy.spatial.distance.sqeuclidean -->
@@ -491,7 +491,7 @@ slow_output_color = (alpha * light_intensity * diffuse_component + beta * specul
491
491
 
492
492
  By default, computations use a single CPU core.
493
493
  To override this behavior, use the `threads` argument.
494
- Set it to `0` to use all available CPU cores.
494
+ Set it to `0` to use all available CPU cores and let the underlying C library manage the thread pool.
495
495
  Here is an example of dealing with large sets of binary vectors:
496
496
 
497
497
  ```py
@@ -507,9 +507,42 @@ distances = simsimd.cdist(matrix1, matrix2,
507
507
  )
508
508
  ```
509
509
 
510
+ Alternatively, when using free-threading Python 3.13t builds, one can combine single-threaded SimSIMD operations with Python's `concurrent.futures.ThreadPoolExecutor` to parallelize the computations.
510
511
  By default, the output distances will be stored in double-precision `float64` floating-point numbers.
511
512
  That behavior may not be space-efficient, especially if you are computing the hamming distance between short binary vectors, that will generally fit into 8x smaller `uint8` or `uint16` types.
512
- To override this behavior, use the `dtype` argument.
513
+ To override this behavior, use the `out_dtype` argument, or consider pre-allocating the output array and passing it to the `out` argument.
514
+ A more complete example may look like this:
515
+
516
+ ```py
517
+ from multiprocessing import cpu_count
518
+ from concurrent.futures import ThreadPoolExecutor
519
+ from simsimd import cosine
520
+ import numpy as np
521
+
522
+ # Generate large dataset
523
+ vectors_a = np.random.rand(100_000, 1536).astype(np.float32)
524
+ vectors_b = np.random.rand(100_000, 1536).astype(np.float32)
525
+ distances = np.zeros((100_000,), dtype=np.float32)
526
+
527
+ def compute_batch(start_idx, end_idx):
528
+ batch_a = vectors_a[start_idx:end_idx]
529
+ batch_b = vectors_b[start_idx:end_idx]
530
+ cosine(batch_a, batch_b, out=distances[start_idx:end_idx])
531
+
532
+ # Use all CPU cores with true parallelism (no GIL!)
533
+ num_threads = cpu_count()
534
+ chunk_size = len(vectors_a) // num_threads
535
+
536
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
537
+ futures = []
538
+ for i in range(num_threads):
539
+ start_idx = i * chunk_size
540
+ end_idx = (i + 1) * chunk_size if i < num_threads - 1 else len(vectors_a)
541
+ futures.append(executor.submit(compute_batch, start_idx, end_idx))
542
+
543
+ # Collect results from all threads
544
+ results = [future.result() for future in futures]
545
+ ```
513
546
 
514
547
  ### Helper Functions
515
548
 
@@ -825,14 +858,17 @@ Simplest of all, you can include the headers, and the compiler will automaticall
825
858
  #include <simsimd/simsimd.h>
826
859
 
827
860
  int main() {
861
+ simsimd_flush_denormals(); // Optional, to avoid performance penalties on denormal numbers
862
+
828
863
  simsimd_f32_t vector_a[1536];
829
864
  simsimd_f32_t vector_b[1536];
830
- simsimd_kernel_punned_t distance_function = simsimd_metric_punned(
865
+ simsimd_kernel_punned_t metric_punned = simsimd_metric_punned(
831
866
  simsimd_metric_cos_k, // Metric kind, like the angular cosine distance
832
867
  simsimd_datatype_f32_k, // Data type, like: f16, f32, f64, i8, b8, and complex variants
833
868
  simsimd_cap_any_k); // Which CPU capabilities are we allowed to use
834
869
  simsimd_distance_t distance;
835
- distance_function(vector_a, vector_b, 1536, &distance);
870
+ simsimd_metric_dense_punned_t metric = (simsimd_metric_dense_punned_t)metric_punned;
871
+ metric(vector_a, vector_b, 1536, &distance);
836
872
  return 0;
837
873
  }
838
874
  ```
@@ -1384,3 +1420,7 @@ $ grep -oP 'SIMSIMD_PUBLIC void simsimd_\w+_\w+_\w+\(' include/simsimd/*.h | sor
1384
1420
  > include/simsimd/binary.h:SIMSIMD_PUBLIC void simsimd_jaccard_b8_serial(
1385
1421
  > include/simsimd/binary.h:SIMSIMD_PUBLIC void simsimd_jaccard_b8_sve(
1386
1422
  ```
1423
+
1424
+ ## License
1425
+
1426
+ Feel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.
package/simsimd/build.rs CHANGED
@@ -1,4 +1,4 @@
1
- fn main() {
1
+ fn main() -> Result<(), cc::Error> {
2
2
  let mut build = cc::Build::new();
3
3
 
4
4
  build
@@ -12,7 +12,7 @@ fn main() {
12
12
  .flag("-pedantic") // Ensure strict compliance with the C standard
13
13
  .warnings(false);
14
14
 
15
- if build.try_compile("simsimd").is_err() {
15
+ if let Err(e) = build.try_compile("simsimd") {
16
16
  print!("cargo:warning=Failed to compile with all SIMD backends...");
17
17
 
18
18
  let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
@@ -39,9 +39,11 @@ fn main() {
39
39
  ],
40
40
  };
41
41
 
42
+ let mut result = Err(e);
42
43
  for flag in flags_to_try.iter() {
43
44
  build.define(flag, "0");
44
- if build.try_compile("simsimd").is_ok() {
45
+ result = build.try_compile("simsimd");
46
+ if result.is_ok() {
45
47
  break;
46
48
  }
47
49
 
@@ -51,6 +53,7 @@ fn main() {
51
53
  flag
52
54
  );
53
55
  }
56
+ result?;
54
57
  }
55
58
 
56
59
  println!("cargo:rerun-if-changed=c/lib.c");
@@ -62,4 +65,5 @@ fn main() {
62
65
  println!("cargo:rerun-if-changed=include/simsimd/probability.h");
63
66
  println!("cargo:rerun-if-changed=include/simsimd/binary.h");
64
67
  println!("cargo:rerun-if-changed=include/simsimd/types.h");
68
+ Ok(())
65
69
  }
package/simsimd/c/lib.c CHANGED
@@ -241,6 +241,7 @@ SIMSIMD_DYNAMIC int simsimd_uses_sapphire(void) { return (simsimd_capabilities()
241
241
  SIMSIMD_DYNAMIC int simsimd_uses_turin(void) { return (simsimd_capabilities() & simsimd_cap_turin_k) != 0; }
242
242
  SIMSIMD_DYNAMIC int simsimd_uses_sierra(void) { return (simsimd_capabilities() & simsimd_cap_sierra_k) != 0; }
243
243
  SIMSIMD_DYNAMIC int simsimd_uses_dynamic_dispatch(void) { return 1; }
244
+ SIMSIMD_DYNAMIC int simsimd_flush_denormals(void) { return _simsimd_flush_denormals(); }
244
245
 
245
246
  SIMSIMD_DYNAMIC simsimd_capability_t simsimd_capabilities(void) {
246
247
  //! The latency of the CPUID instruction can be over 100 cycles, so we cache the result.
@@ -254,7 +255,11 @@ SIMSIMD_DYNAMIC simsimd_capability_t simsimd_capabilities(void) {
254
255
  // with dummy inputs:
255
256
  simsimd_distance_t dummy_results_buffer[2];
256
257
  simsimd_distance_t *dummy_results = &dummy_results_buffer[0];
257
- void *x = 0;
258
+
259
+ // Passing `NULL` as `x` will trigger all kinds of `nonull` warnings on GCC.
260
+ typedef double largest_scalar_t;
261
+ largest_scalar_t dummy_input[1];
262
+ void *x = &dummy_input[0];
258
263
 
259
264
  // Dense:
260
265
  simsimd_dot_i8((simsimd_i8_t *)x, (simsimd_i8_t *)x, 0, dummy_results);
@@ -13,7 +13,7 @@
13
13
  * - x86: Haswell, Ice Lake
14
14
  *
15
15
  * The hardest part of optimizing binary similarity measures is the population count operation.
16
- * It's natively supported by almost every insrtuction set, but the throughput and latency can
16
+ * It's natively supported by almost every instruction set, but the throughput and latency can
17
17
  * be suboptimal. There are several ways to optimize this operation:
18
18
  *
19
19
  * - Lookup tables, mostly using nibbles (4-bit lookups)
@@ -107,8 +107,8 @@ SIMSIMD_PUBLIC void simsimd_jaccard_b8_serial(simsimd_b8_t const *a, simsimd_b8_
107
107
  #if _SIMSIMD_TARGET_ARM
108
108
  #if SIMSIMD_TARGET_NEON
109
109
  #pragma GCC push_options
110
- #pragma GCC target("arch=armv8.2-a+simd")
111
- #pragma clang attribute push(__attribute__((target("arch=armv8.2-a+simd"))), apply_to = function)
110
+ #pragma GCC target("arch=armv8-a+simd")
111
+ #pragma clang attribute push(__attribute__((target("arch=armv8-a+simd"))), apply_to = function)
112
112
 
113
113
  SIMSIMD_INTERNAL simsimd_u32_t _simsimd_reduce_u8x16_neon(uint8x16_t vec) {
114
114
  // Split the vector into two halves and widen to `uint16x8_t`
@@ -244,8 +244,8 @@ SIMSIMD_MAKE_COMPLEX_VDOT(accurate, bf16c, f64, SIMSIMD_BF16_TO_F32) // simsimd_
244
244
  #if _SIMSIMD_TARGET_ARM
245
245
  #if SIMSIMD_TARGET_NEON
246
246
  #pragma GCC push_options
247
- #pragma GCC target("arch=armv8.2-a+simd")
248
- #pragma clang attribute push(__attribute__((target("arch=armv8.2-a+simd"))), apply_to = function)
247
+ #pragma GCC target("arch=armv8-a+simd")
248
+ #pragma clang attribute push(__attribute__((target("arch=armv8-a+simd"))), apply_to = function)
249
249
 
250
250
  SIMSIMD_INTERNAL float32x4_t _simsimd_partial_load_f32x4_neon(simsimd_f32_t const *x, simsimd_size_t n) {
251
251
  union {
@@ -1469,10 +1469,19 @@ simsimd_fma_bf16_skylake_cycle:
1469
1469
 
1470
1470
  #if SIMSIMD_TARGET_SAPPHIRE
1471
1471
  #pragma GCC push_options
1472
- #pragma GCC target("avx2", "avx512f", "avx512vl", "bmi2", "avx512bw", "avx512fp16")
1473
- #pragma clang attribute push(__attribute__((target("avx2,avx512f,avx512vl,bmi2,avx512bw,avx512fp16"))), \
1472
+ #pragma GCC target("avx2", "avx512f", "avx512vl", "bmi2", "avx512bw", "avx512fp16", "f16c")
1473
+ #pragma clang attribute push(__attribute__((target("avx2,avx512f,avx512vl,bmi2,avx512bw,avx512fp16,f16c"))), \
1474
1474
  apply_to = function)
1475
1475
 
1476
+ /**
1477
+ * Using `_mm512_set1_ph((_Float16)1.f)` results in compilation warnings if we are pedantic.
1478
+ * https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/2021-8/details-about-intrinsics-for-half-floats.html
1479
+ */
1480
+ SIMSIMD_INTERNAL __m512h _mm512_set1_ph_from_ps(float a) {
1481
+ unsigned short h = _cvtss_sh(a, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
1482
+ return (__m512h)_mm512_set1_epi16(h);
1483
+ }
1484
+
1476
1485
  SIMSIMD_PUBLIC void simsimd_sum_f16_sapphire(simsimd_f16_t const *a, simsimd_f16_t const *b, simsimd_size_t n,
1477
1486
  simsimd_f16_t *result) {
1478
1487
  __mmask32 mask = 0xFFFFFFFF;
@@ -1500,7 +1509,7 @@ SIMSIMD_PUBLIC void simsimd_scale_f16_sapphire(simsimd_f16_t const *a, simsimd_s
1500
1509
  simsimd_f16_t *result) {
1501
1510
 
1502
1511
  __mmask32 mask = 0xFFFFFFFF;
1503
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1512
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1504
1513
  __m512h a_f16_vec, b_f16_vec;
1505
1514
  __m512h sum_f16_vec;
1506
1515
  simsimd_scale_f16_sapphire_cycle:
@@ -1540,8 +1549,8 @@ SIMSIMD_PUBLIC void simsimd_wsum_f16_sapphire( //
1540
1549
 
1541
1550
  // The general case.
1542
1551
  __mmask32 mask = 0xFFFFFFFF;
1543
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1544
- __m512h beta_vec = _mm512_set1_ph((_Float16)beta);
1552
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1553
+ __m512h beta_vec = _mm512_set1_ph_from_ps(beta);
1545
1554
  __m512h a_f16_vec, b_f16_vec;
1546
1555
  __m512h a_scaled_f16_vec, sum_f16_vec;
1547
1556
  simsimd_wsum_f16_sapphire_cycle:
@@ -1568,8 +1577,8 @@ SIMSIMD_PUBLIC void simsimd_fma_f16_sapphire(
1568
1577
  simsimd_distance_t alpha, simsimd_distance_t beta, simsimd_f16_t *result) {
1569
1578
 
1570
1579
  __mmask32 mask = 0xFFFFFFFF;
1571
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1572
- __m512h beta_vec = _mm512_set1_ph((_Float16)beta);
1580
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1581
+ __m512h beta_vec = _mm512_set1_ph_from_ps(beta);
1573
1582
  __m512h a_f16_vec, b_f16_vec, c_f16_vec;
1574
1583
  __m512h ab_f16_vec, ab_scaled_f16_vec, sum_f16_vec;
1575
1584
  simsimd_fma_f16_sapphire_cycle:
@@ -1619,7 +1628,7 @@ simsimd_sum_u8_sapphire_cycle:
1619
1628
  SIMSIMD_PUBLIC void simsimd_scale_u8_sapphire(simsimd_u8_t const *a, simsimd_size_t n, simsimd_distance_t alpha,
1620
1629
  simsimd_u8_t *result) {
1621
1630
  __mmask64 mask = 0xFFFFFFFFFFFFFFFFull;
1622
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1631
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1623
1632
  __m512i a_u8_vec, b_u8_vec, sum_u8_vec;
1624
1633
  __m512h a_f16_low_vec, a_f16_high_vec;
1625
1634
  __m512h a_scaled_f16_low_vec, a_scaled_f16_high_vec, sum_f16_low_vec, sum_f16_high_vec;
@@ -1670,8 +1679,8 @@ SIMSIMD_PUBLIC void simsimd_wsum_u8_sapphire( //
1670
1679
 
1671
1680
  // The general case.
1672
1681
  __mmask64 mask = 0xFFFFFFFFFFFFFFFFull;
1673
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1674
- __m512h beta_vec = _mm512_set1_ph((_Float16)beta);
1682
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1683
+ __m512h beta_vec = _mm512_set1_ph_from_ps(beta);
1675
1684
  __m512i a_u8_vec, b_u8_vec, sum_u8_vec;
1676
1685
  __m512h a_f16_low_vec, a_f16_high_vec, b_f16_low_vec, b_f16_high_vec;
1677
1686
  __m512h a_scaled_f16_low_vec, a_scaled_f16_high_vec, sum_f16_low_vec, sum_f16_high_vec;
@@ -1739,7 +1748,7 @@ SIMSIMD_PUBLIC void simsimd_scale_i8_sapphire(simsimd_i8_t const *a, simsimd_siz
1739
1748
  simsimd_i8_t *result) {
1740
1749
 
1741
1750
  __mmask64 mask = 0xFFFFFFFFFFFFFFFFull;
1742
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1751
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1743
1752
  __m512i a_i8_vec, sum_i8_vec;
1744
1753
  __m512h a_f16_low_vec, a_f16_high_vec;
1745
1754
  __m512h sum_f16_low_vec, sum_f16_high_vec;
@@ -1791,8 +1800,8 @@ SIMSIMD_PUBLIC void simsimd_wsum_i8_sapphire( //
1791
1800
 
1792
1801
  // The general case.
1793
1802
  __mmask64 mask = 0xFFFFFFFFFFFFFFFFull;
1794
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1795
- __m512h beta_vec = _mm512_set1_ph((_Float16)beta);
1803
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1804
+ __m512h beta_vec = _mm512_set1_ph_from_ps(beta);
1796
1805
  __m512i a_i8_vec, b_i8_vec, sum_i8_vec;
1797
1806
  __m512h a_f16_low_vec, a_f16_high_vec, b_f16_low_vec, b_f16_high_vec;
1798
1807
  __m512h a_scaled_f16_low_vec, a_scaled_f16_high_vec, sum_f16_low_vec, sum_f16_high_vec;
@@ -1836,8 +1845,8 @@ SIMSIMD_PUBLIC void simsimd_fma_i8_sapphire(
1836
1845
  simsimd_distance_t alpha, simsimd_distance_t beta, simsimd_i8_t *result) {
1837
1846
 
1838
1847
  __mmask64 mask = 0xFFFFFFFFFFFFFFFF;
1839
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1840
- __m512h beta_vec = _mm512_set1_ph((_Float16)beta);
1848
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1849
+ __m512h beta_vec = _mm512_set1_ph_from_ps(beta);
1841
1850
  __m512i a_i8_vec, b_i8_vec, c_i8_vec, sum_i8_vec;
1842
1851
  __m512h a_f16_low_vec, a_f16_high_vec, b_f16_low_vec, b_f16_high_vec;
1843
1852
  __m512h c_f16_low_vec, c_f16_high_vec, ab_f16_low_vec, ab_f16_high_vec;
@@ -1889,8 +1898,8 @@ SIMSIMD_PUBLIC void simsimd_fma_u8_sapphire(
1889
1898
  simsimd_distance_t alpha, simsimd_distance_t beta, simsimd_u8_t *result) {
1890
1899
 
1891
1900
  __mmask64 mask = 0xFFFFFFFFFFFFFFFF;
1892
- __m512h alpha_vec = _mm512_set1_ph((_Float16)alpha);
1893
- __m512h beta_vec = _mm512_set1_ph((_Float16)beta);
1901
+ __m512h alpha_vec = _mm512_set1_ph_from_ps(alpha);
1902
+ __m512h beta_vec = _mm512_set1_ph_from_ps(beta);
1894
1903
  __m512i a_u8_vec, b_u8_vec, c_u8_vec, sum_u8_vec;
1895
1904
  __m512h a_f16_low_vec, a_f16_high_vec, b_f16_low_vec, b_f16_high_vec;
1896
1905
  __m512h c_f16_low_vec, c_f16_high_vec, ab_f16_low_vec, ab_f16_high_vec;