usearch 2.26.1 → 2.26.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.
Files changed (146) hide show
  1. package/README.md +1 -1
  2. package/include/usearch/index.hpp +5 -1
  3. package/numkong/CITATION.cff +13 -9
  4. package/numkong/CONTRIBUTING.md +29 -24
  5. package/numkong/Cargo.toml +1 -1
  6. package/numkong/README.md +21 -7
  7. package/numkong/binding.gyp +47 -31
  8. package/numkong/build.rs +15 -5
  9. package/numkong/c/dispatch.h +0 -1
  10. package/numkong/c/parallel.c +139 -0
  11. package/numkong/c/parallel.h +47 -0
  12. package/numkong/cmake/toolchain-wasi.cmake +39 -5
  13. package/numkong/include/README.md +24 -17
  14. package/numkong/include/numkong/attention/sapphireamx.h +6 -6
  15. package/numkong/include/numkong/attention/sme.h +2 -2
  16. package/numkong/include/numkong/capabilities.h +2 -2
  17. package/numkong/include/numkong/cast/README.md +9 -7
  18. package/numkong/include/numkong/cast/loongsonasx.h +48 -40
  19. package/numkong/include/numkong/cast/serial.h +89 -11
  20. package/numkong/include/numkong/cast.h +3 -3
  21. package/numkong/include/numkong/curved/README.md +10 -10
  22. package/numkong/include/numkong/curved/neon.h +1 -0
  23. package/numkong/include/numkong/curved/neonbfdot.h +1 -0
  24. package/numkong/include/numkong/curved/smef64.h +1 -0
  25. package/numkong/include/numkong/dot/README.md +18 -18
  26. package/numkong/include/numkong/dot/alder.h +8 -8
  27. package/numkong/include/numkong/dot/haswell.h +8 -8
  28. package/numkong/include/numkong/dot/icelake.h +12 -12
  29. package/numkong/include/numkong/dot/loongsonasx.h +6 -6
  30. package/numkong/include/numkong/dot/neon.h +2 -1
  31. package/numkong/include/numkong/dot/neonbfdot.h +1 -0
  32. package/numkong/include/numkong/dot/neonfhm.h +1 -0
  33. package/numkong/include/numkong/dot/neonsdot.h +1 -0
  34. package/numkong/include/numkong/dot/powervsx.h +3 -3
  35. package/numkong/include/numkong/dot/serial.h +19 -6
  36. package/numkong/include/numkong/dot/skylake.h +4 -4
  37. package/numkong/include/numkong/dot/svehalf.h +1 -1
  38. package/numkong/include/numkong/dot/v128relaxed.h +24 -23
  39. package/numkong/include/numkong/dots/README.md +26 -26
  40. package/numkong/include/numkong/dots/genoa.h +3 -1
  41. package/numkong/include/numkong/dots/icelake.h +1 -0
  42. package/numkong/include/numkong/dots/powervsx.h +1 -1
  43. package/numkong/include/numkong/dots/serial.h +32 -33
  44. package/numkong/include/numkong/dots/sierra.h +4 -3
  45. package/numkong/include/numkong/each/README.md +5 -3
  46. package/numkong/include/numkong/each/sapphire.h +2 -0
  47. package/numkong/include/numkong/each/skylake.h +1 -0
  48. package/numkong/include/numkong/geospatial/README.md +6 -6
  49. package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
  50. package/numkong/include/numkong/maxsim/README.md +5 -5
  51. package/numkong/include/numkong/maxsim/genoa.h +4 -0
  52. package/numkong/include/numkong/maxsim/icelake.h +1 -0
  53. package/numkong/include/numkong/maxsim/serial.h +22 -9
  54. package/numkong/include/numkong/maxsim/sme.h +3 -2
  55. package/numkong/include/numkong/mesh/README.md +7 -4
  56. package/numkong/include/numkong/mesh/genoa.h +2 -2
  57. package/numkong/include/numkong/mesh/haswell.h +8 -8
  58. package/numkong/include/numkong/mesh/neon.h +6 -6
  59. package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
  60. package/numkong/include/numkong/mesh/neonfhm.h +3 -2
  61. package/numkong/include/numkong/mesh/rvv.h +4 -4
  62. package/numkong/include/numkong/mesh/serial.h +17 -17
  63. package/numkong/include/numkong/mesh/skylake.h +4 -4
  64. package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
  65. package/numkong/include/numkong/mesh.hpp +2 -2
  66. package/numkong/include/numkong/numkong.h +1 -1
  67. package/numkong/include/numkong/numkong.hpp +1 -1
  68. package/numkong/include/numkong/probability/README.md +17 -15
  69. package/numkong/include/numkong/probability/skylake.h +1 -1
  70. package/numkong/include/numkong/reduce/README.md +27 -21
  71. package/numkong/include/numkong/reduce/genoa.h +2 -1
  72. package/numkong/include/numkong/reduce/icelake.h +1 -0
  73. package/numkong/include/numkong/reduce/neon.h +1 -1
  74. package/numkong/include/numkong/reduce/serial.h +9 -9
  75. package/numkong/include/numkong/reduce/skylake.h +4 -4
  76. package/numkong/include/numkong/reduce.hpp +38 -0
  77. package/numkong/include/numkong/scalar/README.md +29 -28
  78. package/numkong/include/numkong/set/README.md +17 -14
  79. package/numkong/include/numkong/set/haswell.h +15 -16
  80. package/numkong/include/numkong/set/icelake.h +15 -16
  81. package/numkong/include/numkong/set/loongsonasx.h +12 -10
  82. package/numkong/include/numkong/set/neon.h +15 -19
  83. package/numkong/include/numkong/set/powervsx.h +15 -21
  84. package/numkong/include/numkong/set/serial.h +19 -20
  85. package/numkong/include/numkong/set/v128relaxed.h +10 -8
  86. package/numkong/include/numkong/sets/icelake.h +1 -0
  87. package/numkong/include/numkong/sparse/README.md +4 -4
  88. package/numkong/include/numkong/sparse/icelake.h +1 -0
  89. package/numkong/include/numkong/sparse/neon.h +1 -0
  90. package/numkong/include/numkong/sparse/serial.h +10 -3
  91. package/numkong/include/numkong/sparse/turin.h +1 -0
  92. package/numkong/include/numkong/spatial/README.md +25 -24
  93. package/numkong/include/numkong/spatial/alder.h +2 -1
  94. package/numkong/include/numkong/spatial/haswell.h +48 -40
  95. package/numkong/include/numkong/spatial/icelake.h +2 -1
  96. package/numkong/include/numkong/spatial/neon.h +54 -46
  97. package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
  98. package/numkong/include/numkong/spatial/powervsx.h +54 -46
  99. package/numkong/include/numkong/spatial/serial.h +56 -46
  100. package/numkong/include/numkong/spatial/sierra.h +4 -3
  101. package/numkong/include/numkong/spatial/skylake.h +27 -22
  102. package/numkong/include/numkong/spatial/sve.h +6 -6
  103. package/numkong/include/numkong/spatial/svebfdot.h +1 -1
  104. package/numkong/include/numkong/spatial/svehalf.h +1 -1
  105. package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
  106. package/numkong/include/numkong/spatials/README.md +15 -15
  107. package/numkong/include/numkong/spatials/genoa.h +3 -0
  108. package/numkong/include/numkong/spatials/graniteamx.h +2 -0
  109. package/numkong/include/numkong/spatials/icelake.h +1 -0
  110. package/numkong/include/numkong/spatials/sierra.h +1 -0
  111. package/numkong/include/numkong/tensor.hpp +236 -123
  112. package/numkong/include/numkong/trigonometry/README.md +4 -4
  113. package/numkong/include/numkong/trigonometry/serial.h +6 -6
  114. package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
  115. package/numkong/include/numkong/types.hpp +46 -16
  116. package/numkong/include/numkong/vector.hpp +116 -23
  117. package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
  118. package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
  119. package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
  120. package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
  121. package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
  122. package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
  123. package/numkong/javascript/README.md +18 -16
  124. package/numkong/javascript/numkong.c +70 -40
  125. package/numkong/package.json +8 -8
  126. package/numkong/rust/README.md +19 -9
  127. package/numkong/rust/cast.rs +52 -1
  128. package/numkong/rust/each.rs +480 -7
  129. package/numkong/rust/matrix.rs +877 -216
  130. package/numkong/rust/numkong.rs +12 -8
  131. package/numkong/rust/reduce.rs +433 -5
  132. package/numkong/rust/tensor.rs +450 -842
  133. package/numkong/rust/vector.rs +520 -101
  134. package/numkong/swift/README.md +5 -3
  135. package/numkong/test/README.md +4 -4
  136. package/numkong/test/test-wasi.mjs +17 -0
  137. package/numkong/test/test_each.cpp +30 -1
  138. package/numkong/test/test_each.py +74 -1
  139. package/numkong/test/test_reduce.cpp +51 -0
  140. package/numkong/test/test_tensor.cpp +2 -63
  141. package/numkong/test/test_tensor.py +93 -0
  142. package/package.json +1 -1
  143. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  144. package/prebuilds/linux-arm64/usearch.node +0 -0
  145. package/prebuilds/linux-x64/usearch.node +0 -0
  146. package/prebuilds/win32-x64/usearch.node +0 -0
package/README.md CHANGED
@@ -549,7 +549,7 @@ doi = {10.5281/zenodo.7949415},
549
549
  author = {Vardanian, Ash},
550
550
  title = {{USearch by Unum Cloud}},
551
551
  url = {https://github.com/unum-cloud/USearch},
552
- version = {2.26.1},
552
+ version = {2.26.2},
553
553
  year = {2026},
554
554
  }
555
555
  ```
@@ -9,7 +9,7 @@
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
11
  #define USEARCH_VERSION_MINOR 26
12
- #define USEARCH_VERSION_PATCH 1
12
+ #define USEARCH_VERSION_PATCH 2
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
@@ -2084,6 +2084,10 @@ class memory_mapped_file_t {
2084
2084
 
2085
2085
  #if defined(USEARCH_DEFINED_LINUX)
2086
2086
  int descriptor = open(path_, O_RDONLY | O_NOATIME);
2087
+ if (descriptor < 0 && errno == EPERM)
2088
+ // `O_NOATIME` requires owning the file or holding `CAP_FOWNER` (see `open(2)`).
2089
+ // Retry without it instead of failing outright.
2090
+ descriptor = open(path_, O_RDONLY);
2087
2091
  #else
2088
2092
  int descriptor = open(path_, O_RDONLY);
2089
2093
  #endif
@@ -3,18 +3,22 @@ title: "NumKong: 2000 Mixed Precision Kernels For All"
3
3
  message: "If you use this software, please cite it as below."
4
4
  type: software
5
5
  license: Apache-2.0
6
- version: 7.6.0
6
+ version: 7.8.1
7
7
  repository-code: https://github.com/ashvardanian/NumKong
8
8
  url: https://ashvardanian.com/posts/numkong/
9
+ doi: 10.5281/zenodo.21480519
10
+ identifiers:
11
+ - type: doi
12
+ value: 10.5281/zenodo.21839607
13
+ description: "v7.8.0 — version DOI"
9
14
  abstract: >-
10
- Portable mixed-precision math, linear-algebra, & retrieval library
11
- with 2'000+ SIMD kernels for x86, Arm, RISC-V, LoongArch, Power, & WebAssembly,
12
- leveraging rare algebraic transforms with both 1D & 2D registers like AMX & SME,
13
- covering 15+ numeric types from 4-bit integers & 6-bit floats to 128-bit complex numbers,
14
- validated against 118-bit extended-precision baselines
15
- with saturation, casting, & rounding edge-case coverage,
16
- in a 5-100x smaller binary than other BLAS-like alternatives,
17
- co-designed with Tensor abstractions in C++, Python, Rust, JavaScript, GoLang, & Swift.
15
+ Portable mixed-precision math, linear-algebra, & retrieval library with 2'000+ SIMD kernels for
16
+ x86, Arm, RISC-V, LoongArch, Power, & WebAssembly, leveraging rare algebraic transforms with
17
+ both 1D & 2D registers like AMX & SME, covering 15+ numeric types from 4-bit integers & 6-bit
18
+ floats to 128-bit complex numbers, validated against 118-bit extended-precision baselines with
19
+ saturation, casting, & rounding edge-case coverage, in a 5-100x smaller binary than other
20
+ BLAS-like alternatives, co-designed with Tensor abstractions in C++, Python, Rust, JavaScript,
21
+ GoLang, & Swift.
18
22
 
19
23
  authors:
20
24
  - family-names: Vardanian
@@ -32,21 +32,20 @@ build_release/nk_bench
32
32
  build_release/nk_test
33
33
  ```
34
34
 
35
- | CMake Flag | Default | Description |
36
- | -------------------------- | ------------------ | ------------------------------------------------------ |
37
- | `NK_BUILD_TEST` | `OFF` | Compile precision tests with ULP error analysis |
38
- | `NK_BUILD_BENCH` | `OFF` | Compile micro-benchmarks |
39
- | `NK_BUILD_SHARED` | `ON`, if top-level | Compile dynamic library |
40
- | `NK_BUILD_SHARED_TEST` | `OFF` | Compile tests against the shared library |
41
- | `NK_COMPARE_TO_BLAS` | `AUTO` | Include OpenBLAS into test/bench comparisons |
42
- | `NK_COMPARE_TO_ACCELERATE` | `AUTO` | Include Apple's Accelerate into test/bench comparisons |
43
- | `NK_COMPARE_TO_MKL` | `AUTO` | Include Intel' MKL into test/bench comparisons |
44
- | `NK_MARCH_NATIVE` | `OFF` | Tune for host CPU with `-march=native` |
35
+ | CMake Flag | Default | Description |
36
+ | ---------------------- | ------------------ | ----------------------------------------------------------------------------- |
37
+ | `NK_BUILD_TEST` | `OFF` | Compile precision tests with ULP error analysis |
38
+ | `NK_BUILD_BENCH` | `OFF` | Compile micro-benchmarks |
39
+ | `NK_BUILD_SHARED` | `ON`, if top-level | Compile dynamic library |
40
+ | `NK_BUILD_SHARED_TEST` | `OFF` | Compile tests against the shared library |
41
+ | `NK_COMPARE_TO_BLAS` | `AUTO` | Include OpenBLAS, or Apple's Accelerate on macOS, into test/bench comparisons |
42
+ | `NK_COMPARE_TO_MKL` | `AUTO` | Include Intel' MKL into test/bench comparisons |
43
+ | `NK_MARCH_NATIVE` | `OFF` | Tune for host CPU with `-march=native` |
45
44
 
46
45
  ### Target Baseline Policy
47
46
 
48
47
  `CMakeLists.txt`, `build.rs`, `setup.py`, and `binding.gyp` pin the TU-level baseline to each architecture's ABI floor so distributable artifacts run on any CPU matching the ABI, not just the build host.
49
- SIMD kernels live inside `#pragma GCC target(...)` regions and are only called after runtime probing — see the README's [Compile-Time and Run-Time Dispatch](../README.md#compile-time-and-run-time-dispatch) section.
48
+ SIMD kernels live inside `#pragma GCC target(...)` regions and are only called after runtime probing — see the README's [Compile-Time and Run-Time Dispatch](README.md#compile-time-and-run-time-dispatch) section.
50
49
 
51
50
  | Target arch | GCC/Clang baseline | MSVC baseline | Notes |
52
51
  | ------------- | --------------------------- | --------------- | ----------------------------------------------------------- |
@@ -89,19 +88,25 @@ sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100
89
88
 
90
89
  ### Cross-Compilation
91
90
 
92
- NumKong ships 8 toolchain files in `cmake/` for cross-compiling to non-native targets.
91
+ NumKong ships 11 toolchain files in `cmake/`, each named `toolchain-<name>.cmake`.
93
92
  Tests and benchmarks run transparently under QEMU via `CMAKE_CROSSCOMPILING_EMULATOR`.
94
-
95
- | Target | Toolchain File | Emulator | Prerequisites |
96
- | ------------------------- | ------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------ |
97
- | ARM64 Linux | `toolchain-aarch64-gnu.cmake` | `qemu-aarch64 -cpu max` | `gcc-aarch64-linux-gnu`, `qemu-user` |
98
- | RISC-V 64 GCC | `toolchain-riscv64-gnu.cmake` | `qemu-riscv64 -cpu max` | [riscv-gnu-toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain/releases), `qemu-user` |
99
- | RISC-V 64 LLVM | `toolchain-riscv64-llvm.cmake` | `qemu-riscv64 -cpu max` | LLVM 17+, `RISCV_SYSROOT` |
100
- | Android ARM64 | `toolchain-android-arm64.cmake` | — | `ANDROID_NDK_ROOT` |
101
- | x86_64 from Apple Silicon | `toolchain-x86_64-llvm.cmake` | `arch -x86_64` | Homebrew LLVM |
102
- | WASM Emscripten | `toolchain-wasm.cmake` | Node.js | Emscripten 3.1.27+ |
103
- | WASM64 Memory64 | `toolchain-wasm64.cmake` | Node.js | Emscripten 3.1.35+ |
104
- | WASI | `toolchain-wasi.cmake` | Wasmtime / Wasmer | WASI SDK 24+ |
93
+ Targets with a `qemu-*` emulator additionally require `qemu-user`.
94
+
95
+ | Target | Toolchain | Emulator | Prerequisites |
96
+ | ----------------------- | ----------------- | --------------------------- | ------------------------------ |
97
+ | ARM64 Linux | `aarch64-gnu` | `qemu-aarch64 -cpu max` | `gcc-aarch64-linux-gnu` |
98
+ | RISC-V 64 GCC | `riscv64-gnu` | `qemu-riscv64 -cpu max` | [`riscv-gnu-toolchain`][riscv] |
99
+ | RISC-V 64 LLVM | `riscv64-llvm` | `qemu-riscv64 -cpu max` | LLVM 17+, `RISCV_SYSROOT` |
100
+ | ppc64le Linux | `ppc64le-gnu` | `qemu-ppc64le -cpu power9` | `gcc-powerpc64le-linux-gnu` |
101
+ | LoongArch 64 | `loongarch64-gnu` | `qemu-loongarch64 -cpu max` | `LOONGARCH_TOOLCHAIN_PATH` |
102
+ | Android ARM64 | `android-arm64` | — | `ANDROID_NDK_ROOT` |
103
+ | Android ARMv7 | `android-armv7` | — | `ANDROID_NDK_ROOT` |
104
+ | x86_64 on Apple Silicon | `x86_64-llvm` | `arch -x86_64` | Homebrew LLVM |
105
+ | WASM Emscripten | `wasm` | Node.js | Emscripten 3.1.27+ |
106
+ | WASM64 Memory64 | `wasm64` | Node.js | Emscripten 3.1.35+ |
107
+ | WASI | `wasi` | Wasmtime / Wasmer | WASI SDK 24+ |
108
+
109
+ [riscv]: https://github.com/riscv-collab/riscv-gnu-toolchain/releases
105
110
 
106
111
  Set `NK_IN_QEMU=1` to relax half-precision accuracy thresholds under emulation.
107
112
 
@@ -236,7 +241,7 @@ See [test/README.md](test/README.md) for test framework details and [bench/READM
236
241
  Once done editing the code, please run analyzers and formatters:
237
242
 
238
243
  ```bash
239
- git ls-files '*.h' '*.c' '*.hpp' '*.cpp' | xargs clang-format -i # Use Clang Format 21 or newer
244
+ git ls-files '*.h' '*.c' '*.hpp' '*.cpp' | xargs clang-format -i # Use Clang Format 22 or newer
240
245
  ```
241
246
 
242
247
  ## Python
@@ -19,7 +19,7 @@ name = "numkong"
19
19
  readme = "rust/README.md"
20
20
  repository = "https://github.com/ashvardanian/NumKong"
21
21
  rust-version = "1.64" # Introduced Core C FFI in stable Rust
22
- version = "7.6.0"
22
+ version = "7.8.1"
23
23
 
24
24
  [lib]
25
25
  name = "numkong"
package/numkong/README.md CHANGED
@@ -68,12 +68,12 @@ A broader throughput comparison is maintained in [NumWars](https://github.com/as
68
68
  | Python | `pip install` | Linux, macOS, Windows | [python/README.md](python/README.md) |
69
69
  | Rust | `cargo add` | Linux, macOS, Windows | [rust/README.md](rust/README.md) |
70
70
  | JS | `npm install` & `import` | Node.js, Bun, Deno & browsers | [javascript/README.md](javascript/README.md) |
71
- | Swift | Swift Package Manager | macOS, iOS, tvOS, watchOS | [swift/README.md](swift/README.md) |
71
+ | Swift | Swift Package Manager | Apple platforms & Linux | [swift/README.md](swift/README.md) |
72
72
  | Go | `go get` | Linux, macOS, Windows via cGo | [golang/README.md](golang/README.md) |
73
73
 
74
74
  ## What's Inside
75
75
 
76
- NumKong covers 17 numeric types — from 6-bit floats to 64-bit complex numbers — across dozens of operations and 30+ SIMD backends, with hardware-aware defaults: Arm prioritizes `f16`, x86 prioritizes `bf16`.
76
+ NumKong covers 17 numeric types — from 6-bit floats to 128-bit complex numbers — across dozens of operations and 30+ SIMD backends, with hardware-aware defaults: Arm prioritizes `f16`, x86 prioritizes `bf16`.
77
77
 
78
78
  ### Language Bindings
79
79
 
@@ -215,7 +215,7 @@ The [GIL](https://docs.python.org/3/glossary.html#term-global-interpreter-lock)
215
215
  import concurrent.futures, numkong as nk, numpy as np
216
216
 
217
217
  vectors, num_threads = np.random.randn(1000, 768).astype(np.float32), 4
218
- output = nk.zeros((1000, 1000), dtype="float32")
218
+ output = nk.zeros((1000, 1000), dtype="float64")
219
219
 
220
220
  def compute_slice(t):
221
221
  start = t * (len(vectors) // num_threads)
@@ -320,7 +320,7 @@ Every such decision — saturation thresholds, Newton-Raphson iteration counts,
320
320
 
321
321
  ### Calling Convention & Error Handling
322
322
 
323
- NumKong never throws exceptions, never sets `errno`, and never calls `setjmp`/`longjmp` — [exceptions bloat call sites with unwind tables](https://monkeywritescode.blogspot.com/p/c-exceptions-under-hood.html) and are invisible to C, Python, Rust, Swift, Go, and JavaScript FFI; `errno` is thread-local state whose [storage model varies across C runtimes](https://en.cppreference.com/w/c/error/errno).
323
+ NumKong never throws exceptions, never sets `errno`, and never calls `setjmp`/`longjmp` on any kernel path — [exceptions bloat call sites with unwind tables](https://monkeywritescode.blogspot.com/p/c-exceptions-under-hood.html) and are invisible to C, Python, Rust, Swift, Go, and JavaScript FFI; `errno` is thread-local state whose [storage model varies across C runtimes](https://en.cppreference.com/w/c/error/errno).
324
324
  Instead, every function takes inputs as `const` pointers, writes outputs through caller-provided pointers, and returns `void`:
325
325
 
326
326
  ```c
@@ -352,7 +352,7 @@ nk_capability_t used = nk_cap_serial_k;
352
352
  nk_find_kernel_punned(
353
353
  nk_kernel_angular_k, nk_f32_k, // what functionality? for which input type?
354
354
  nk_capabilities(), // which capabilities are viable?
355
- (nk_kernel_punned_t *)&angular, &used); // the kernel found and capabilties used!
355
+ (nk_kernel_punned_t *)&angular, &used); // the kernel found and capabilities used!
356
356
  ```
357
357
 
358
358
  The first call to `nk_capabilities()` initializes the dispatch table; all subsequent calls are lock-free.
@@ -489,8 +489,8 @@ On Arm, NEON FHM extensions bring widening `FMLAL` dot-products for Float16 —
489
489
  > On NEON+FP8DOT, E3M2 is first promoted to E5M2 and E2M3 to E4M3 before the hardware `FDOT` instruction.
490
490
  > Sierra Forest and Alder Lake use native `VPDPBSSD` (signed×signed) and `VPDPBUSD` (unsigned×signed) respectively for E2M3.
491
491
 
492
- E4M3 and E5M2 cannot use the integer path.
493
- E4M3 scaled by 16 reaches 7,680 — too large for Int8, barely fitting Int16 with a 128-entry table.
492
+ E4M3 and E5M2 cannot use the same 32-entry LUT integer path.
493
+ E4M3 scaled by 16 reaches 7,168 — too large for Int8, barely fitting Int16 with a 128-entry table.
494
494
  E5M2's range (±57,344) makes the scaled product exceed Int32 entirely.
495
495
  Without the integer path, E5M2 falls back to Float32 accumulation — where its [2-bit mantissa (only 4 values per binade)](https://developer.nvidia.com/blog/floating-point-8-an-introduction-to-efficient-lower-precision-ai-training/) creates a [catastrophic cancellation risk](https://www.ac.uma.es/arith2024/papers/Fused%20FP8%204-Way%20Dot%20Product%20with%20Scaling%20and%20FP32%20Accumulation.pdf) that E2M3's integer path avoids completely:
496
496
 
@@ -564,6 +564,20 @@ Beyond the READMEs in this repository, there are several standalone articles cov
564
564
  - [Accelerating JavaScript arrays by 10x for Vector Search](https://ashvardanian.com/posts/javascript-ai-vector-search/)
565
565
  - [SciPy distances... up to 200x faster with AVX-512 & SVE](https://ashvardanian.com/posts/simsimd-faster-scipy/)
566
566
 
567
+ ## Citation
568
+
569
+ If NumKong helps your research or product, please cite it:
570
+
571
+ ```bibtex
572
+ @software{Vardanian_NumKong,
573
+ author = {Vardanian, Ash},
574
+ title = {{NumKong: 2000 Mixed Precision Kernels For All}},
575
+ doi = {10.5281/zenodo.21480519},
576
+ url = {https://github.com/ashvardanian/NumKong},
577
+ license = {Apache-2.0}
578
+ }
579
+ ```
580
+
567
581
  ## License
568
582
 
569
583
  Feel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "variables": {
3
- "openssl_fips": ""
3
+ "openssl_fips": "",
4
+ # `NK_MARCH_NATIVE=1` opts into a host-tuned, non-portable build, as in CMakeLists.txt.
5
+ "nk_march_native%": "<!(node -p \"['1','true','TRUE'].includes(process.env.NK_MARCH_NATIVE||'')?1:0\")"
4
6
  },
5
7
  "targets": [
6
8
  {
@@ -8,6 +10,7 @@
8
10
  "sources": [
9
11
  "javascript/numkong.c",
10
12
  "c/numkong.c",
13
+ "c/parallel.c",
11
14
  "c/dispatch_f64.c",
12
15
  "c/dispatch_f32.c",
13
16
  "c/dispatch_f16.c",
@@ -34,18 +37,17 @@
34
37
  "c/dispatch_e3m2.c",
35
38
  ],
36
39
  "include_dirs": [
37
- "include"
40
+ "include",
41
+ "c"
38
42
  ],
39
43
  "defines": [
40
44
  "NK_NATIVE_F16=0",
41
45
  "NK_NATIVE_BF16=0",
42
- "NK_DYNAMIC_DISPATCH=1",
43
- "NK_USE_OPENMP=1"
46
+ "NK_DYNAMIC_DISPATCH=1"
44
47
  ],
45
48
  "cflags": [
46
49
  "-std=c11",
47
50
  "-O3",
48
- "-fopenmp",
49
51
  "-Wno-unknown-pragmas",
50
52
  "-Wno-maybe-uninitialized",
51
53
  "-Wno-cast-function-type",
@@ -54,25 +56,35 @@
54
56
  "-include",
55
57
  "<(module_root_dir)/nk_probes.h",
56
58
  ],
57
- "ldflags": [
58
- "-fopenmp"
59
- ],
60
59
  "msvs_settings": {
61
60
  "VCCLCompilerTool": {
62
61
  "ForcedIncludeFiles": [
63
62
  "<(module_root_dir)/nk_probes.h"
64
63
  ],
65
64
  "AdditionalOptions": [
66
- "/Zc:preprocessor",
67
- "/openmp:llvm"
65
+ "/Zc:preprocessor"
68
66
  ],
69
67
  },
70
68
  },
71
69
  "conditions": [
70
+ # Only this branch gets OpenMP; macOS and Windows use their own pools.
71
+ [
72
+ "OS!='mac' and OS!='win'",
73
+ {
74
+ "cflags": [
75
+ "-fopenmp"
76
+ ],
77
+ "ldflags": [
78
+ "-fopenmp"
79
+ ]
80
+ }
81
+ ],
72
82
  # Pin TU baseline to each arch's ABI floor; SIMD kernels use per-function pragmas.
73
- # Keep per-arch table in sync with cmake/nk_compiler_flags.cmake, build.rs, setup.py.
83
+ # Keep per-arch table in sync with CMakeLists.txt, build.rs, setup.py.
84
+ # macOS is excluded: `-arch` already pins the slice, and a per-arch `-march=`
85
+ # conflicts with the other slice of a universal build.
74
86
  [
75
- "OS!='win' and target_arch=='arm64'",
87
+ "nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='arm64'",
76
88
  {
77
89
  "cflags": [
78
90
  "-march=armv8-a"
@@ -80,7 +92,7 @@
80
92
  }
81
93
  ],
82
94
  [
83
- "OS!='win' and target_arch=='x64'",
95
+ "nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='x64'",
84
96
  {
85
97
  "cflags": [
86
98
  "-march=x86-64"
@@ -88,7 +100,7 @@
88
100
  }
89
101
  ],
90
102
  [
91
- "OS!='win' and target_arch=='riscv64'",
103
+ "nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='riscv64'",
92
104
  {
93
105
  "cflags": [
94
106
  "-march=rv64gc"
@@ -96,7 +108,7 @@
96
108
  }
97
109
  ],
98
110
  [
99
- "OS!='win' and target_arch=='ppc64'",
111
+ "nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='ppc64'",
100
112
  {
101
113
  "cflags": [
102
114
  "-mcpu=power8"
@@ -104,7 +116,7 @@
104
116
  }
105
117
  ],
106
118
  [
107
- "OS!='win' and target_arch=='loong64'",
119
+ "nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='loong64'",
108
120
  {
109
121
  "cflags": [
110
122
  "-march=loongarch64",
@@ -112,6 +124,14 @@
112
124
  ]
113
125
  }
114
126
  ],
127
+ [
128
+ "nk_march_native==1 and OS!='win' and OS!='mac'",
129
+ {
130
+ "cflags": [
131
+ "-march=native"
132
+ ]
133
+ }
134
+ ],
115
135
  # Forbid auto-vectorization so serial fallbacks don't get silently
116
136
  # promoted to NEON/SSE2/VSX. SIMD kernels use explicit intrinsics
117
137
  # and per-function `target` pragmas; unaffected. MSVC has no
@@ -125,27 +145,23 @@
125
145
  ]
126
146
  }
127
147
  ],
148
+ # gyp ignores `cflags` on the mac flavor, so every flag above must be
149
+ # repeated here or the addon builds unoptimized and without the probes.
128
150
  [
129
151
  "OS=='mac'",
130
152
  {
131
153
  "xcode_settings": {
132
154
  "MACOSX_DEPLOYMENT_TARGET": "11.0",
133
- # Apple Clang ships no `omp.h`; the CI step
134
- # `brew install libomp` makes it keg-only under
135
- # `/opt/homebrew/opt/libomp` (arm64) or
136
- # `/usr/local/opt/libomp` (x86_64). Clang silently
137
- # ignores `-I` / `-L` dirs that don't exist, so
138
- # listing both keeps the file arch-agnostic.
139
155
  "OTHER_CFLAGS": [
140
- "-Xpreprocessor",
141
- "-fopenmp",
142
- "-I/opt/homebrew/opt/libomp/include",
143
- "-I/usr/local/opt/libomp/include"
144
- ],
145
- "OTHER_LDFLAGS": [
146
- "-lomp",
147
- "-L/opt/homebrew/opt/libomp/lib",
148
- "-L/usr/local/opt/libomp/lib"
156
+ "-std=c11",
157
+ "-O3",
158
+ "-fno-tree-vectorize",
159
+ "-fno-tree-slp-vectorize",
160
+ "-Wno-unknown-pragmas",
161
+ "-Wno-cast-function-type",
162
+ "-Wno-switch",
163
+ "-include",
164
+ "<(module_root_dir)/nk_probes.h"
149
165
  ]
150
166
  }
151
167
  }
package/numkong/build.rs CHANGED
@@ -376,21 +376,31 @@ fn build_numkong() -> Result<HashMap<String, bool>, String> {
376
376
 
377
377
  // Pin TU baseline to each arch's ABI floor; SIMD kernels carry per-function pragmas.
378
378
  // `NK_MARCH_NATIVE=1` opts into a host-tuned, non-portable build (ignored on MSVC).
379
- // Keep per-arch table in sync with cmake/nk_compiler_flags.cmake, setup.py, binding.gyp.
380
- let march_native = env::var("NK_MARCH_NATIVE").is_ok_and(|v| v == "1" || v == "true");
379
+ // Keep per-arch table in sync with CMakeLists.txt, setup.py, binding.gyp.
380
+ let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
381
+ let is_apple = target_os == "macos" || target_os == "ios";
382
+ let is_cross = env::var("HOST").unwrap_or_default() != env::var("TARGET").unwrap_or_default();
383
+ let march_native = env::var("NK_MARCH_NATIVE").is_ok_and(|v| v == "1" || v == "true" || v == "TRUE");
381
384
  // Portable baseline: pin TU ISA floor + forbid auto-vectorization so serial
382
385
  // fallbacks don't get silently promoted to NEON/SSE2/VSX. SIMD kernels use
383
386
  // explicit intrinsics; unaffected. MSVC has no command-line vectorizer
384
387
  // toggle; `NK_MARCH_NATIVE=1` opts out for host-tuned builds.
385
- if march_native && !is_msvc {
386
- println!("cargo:warning=NK_MARCH_NATIVE=1: building -march=native, result will not run on older CPUs");
387
- build.flag_if_supported("-march=native");
388
+ if march_native && !is_msvc && !is_cross {
389
+ println!("cargo:warning=NK_MARCH_NATIVE=1: building host-tuned, result will not run on older CPUs");
390
+ // Apple Clang's `-march=native` advertises only a subset of host features
391
+ // (no SME/SME2/FP16_FML); `-mcpu=native` is the complete knob on macOS.
392
+ build.flag_if_supported(if is_apple { "-mcpu=native" } else { "-march=native" });
388
393
  } else if is_msvc {
389
394
  match target_arch.as_str() {
390
395
  "x86_64" => { build.flag_if_supported("/arch:SSE2"); }
391
396
  "aarch64" => { build.flag_if_supported("/arch:armv8.0"); }
392
397
  _ => {}
393
398
  }
399
+ } else if is_apple {
400
+ // `-arch` already pins the ABI floor per slice, and a universal build passes both
401
+ // to one clang invocation, where a per-arch `-march=` conflicts with the other slice.
402
+ build.flag_if_supported("-fno-tree-vectorize");
403
+ build.flag_if_supported("-fno-tree-slp-vectorize");
394
404
  } else {
395
405
  build.flag_if_supported("-fno-tree-vectorize");
396
406
  build.flag_if_supported("-fno-tree-slp-vectorize");
@@ -33,7 +33,6 @@
33
33
  extern "C" {
34
34
  #endif
35
35
 
36
- // Forward declaration of dispatch table type (same structure as in numkong.c)
37
36
  typedef struct {
38
37
  // Dot products
39
38
  nk_metric_dense_punned_t dot_f64c;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @brief Tile-parallel execution for NumKong language bindings.
3
+ * @file c/parallel.c
4
+ * @author Ash Vardanian
5
+ * @date August 6, 2026
6
+ *
7
+ * One shared tile counter drives every backend, so dynamic scheduling and the
8
+ * thread-count cap behave identically no matter which pool runs the tiles.
9
+ */
10
+ #include "parallel.h"
11
+
12
+ #if defined(__APPLE__)
13
+ #include <dispatch/dispatch.h> // `dispatch_apply_f`, part of libSystem
14
+ #include <unistd.h> // `sysconf`
15
+ #elif defined(_WIN32)
16
+ #include <windows.h> // `CreateThreadpoolWork`, part of kernel32
17
+ #else
18
+ #include <unistd.h> // `sysconf`
19
+ #if defined(_OPENMP)
20
+ #include <omp.h>
21
+ #endif
22
+ #endif
23
+
24
+ /* OpenMP schedules tiles itself, so the counter below is for the other pools. */
25
+ #if !defined(__APPLE__) && !defined(_WIN32) && defined(_OPENMP)
26
+ #define NK_PARALLEL_VIA_OPENMP 1
27
+ #else
28
+ #define NK_PARALLEL_VIA_OPENMP 0
29
+ #endif
30
+
31
+ #if !NK_PARALLEL_VIA_OPENMP
32
+
33
+ #pragma region Tile Queue
34
+
35
+ /** @brief Tiles remaining, plus the body that consumes them. */
36
+ typedef struct nk_tile_queue_t {
37
+ nk_tile_body_t body;
38
+ void *context;
39
+ nk_size_t tile_count;
40
+ #if defined(_MSC_VER)
41
+ volatile long long next_tile;
42
+ #else
43
+ nk_size_t next_tile;
44
+ #endif
45
+ } nk_tile_queue_t;
46
+
47
+ /** @brief Claim and run tiles until the queue is empty. Safe to call from every worker at once. */
48
+ static void nk_tile_queue_drain_(nk_tile_queue_t *queue) {
49
+ for (;;) {
50
+ #if defined(_MSC_VER)
51
+ nk_size_t const tile_index = (nk_size_t)_InterlockedExchangeAdd64(&queue->next_tile, 1);
52
+ #else
53
+ nk_size_t const tile_index = __atomic_fetch_add(&queue->next_tile, 1, __ATOMIC_RELAXED);
54
+ #endif
55
+ if (tile_index >= queue->tile_count) return;
56
+ queue->body(tile_index, queue->context);
57
+ }
58
+ }
59
+
60
+ #pragma endregion Tile Queue
61
+
62
+ #endif // !NK_PARALLEL_VIA_OPENMP
63
+
64
+ #pragma region Platform Pools
65
+
66
+ #if defined(__APPLE__)
67
+
68
+ /** @brief libdispatch hands each worker its index; the queue decides what it runs. */
69
+ static void nk_tile_worker_dispatch_(void *context, size_t worker_index) {
70
+ (void)worker_index;
71
+ nk_tile_queue_drain_((nk_tile_queue_t *)context);
72
+ }
73
+
74
+ #elif defined(_WIN32)
75
+
76
+ static void CALLBACK nk_tile_worker_threadpool_(PTP_CALLBACK_INSTANCE instance, PVOID context, PTP_WORK work) {
77
+ (void)instance;
78
+ (void)work;
79
+ nk_tile_queue_drain_((nk_tile_queue_t *)context);
80
+ }
81
+
82
+ #endif
83
+
84
+ nk_size_t nk_parallel_concurrency(void) {
85
+ #if defined(_WIN32)
86
+ DWORD const count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
87
+ return count ? (nk_size_t)count : 1;
88
+ #elif defined(_SC_NPROCESSORS_ONLN)
89
+ long const count = sysconf(_SC_NPROCESSORS_ONLN);
90
+ return count > 0 ? (nk_size_t)count : 1;
91
+ #else
92
+ return 1;
93
+ #endif
94
+ }
95
+
96
+ void nk_parallel_for_tiles(nk_size_t tile_count, nk_size_t threads, nk_tile_body_t body, void *context) {
97
+ if (!tile_count) return;
98
+ if (threads == 0) threads = nk_parallel_concurrency();
99
+ if (threads > tile_count) threads = tile_count;
100
+
101
+ // A single worker needs no pool, no atomics, and no launch latency.
102
+ if (threads <= 1) {
103
+ for (nk_size_t tile_index = 0; tile_index < tile_count; ++tile_index) body(tile_index, context);
104
+ return;
105
+ }
106
+
107
+ #if NK_PARALLEL_VIA_OPENMP
108
+ // OpenMP's own dynamic queue beats draining a shared counter.
109
+ long long const tile_limit = (long long)tile_count;
110
+ #pragma omp parallel for schedule(dynamic, 1) num_threads((int)threads)
111
+ for (long long tile_index = 0; tile_index < tile_limit; ++tile_index) body((nk_size_t)tile_index, context);
112
+ #else
113
+ nk_tile_queue_t queue;
114
+ queue.body = body;
115
+ queue.context = context;
116
+ queue.tile_count = tile_count;
117
+ queue.next_tile = 0;
118
+
119
+ #if defined(__APPLE__)
120
+ dispatch_apply_f((size_t)threads, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), &queue,
121
+ nk_tile_worker_dispatch_);
122
+ #elif defined(_WIN32)
123
+ PTP_WORK work = CreateThreadpoolWork(nk_tile_worker_threadpool_, &queue, NULL);
124
+ if (!work) {
125
+ nk_tile_queue_drain_(&queue); // Out of pool resources — the caller still needs the result
126
+ return;
127
+ }
128
+ // The calling thread takes a share too, so only the extra workers are submitted.
129
+ for (nk_size_t worker = 1; worker < threads; ++worker) SubmitThreadpoolWork(work);
130
+ nk_tile_queue_drain_(&queue);
131
+ WaitForThreadpoolWorkCallbacks(work, FALSE);
132
+ CloseThreadpoolWork(work);
133
+ #else
134
+ nk_tile_queue_drain_(&queue);
135
+ #endif
136
+ #endif // NK_PARALLEL_VIA_OPENMP
137
+ }
138
+
139
+ #pragma endregion Platform Pools
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @brief Tile-parallel execution for NumKong language bindings.
3
+ * @file c/parallel.h
4
+ * @author Ash Vardanian
5
+ * @date August 6, 2026
6
+ *
7
+ * Compiled only by `setup.py` and `binding.gyp`, never by CMake or Cargo.
8
+ * Each platform's own pool runs the tiles, so no binding ships a `libomp`.
9
+ */
10
+ #ifndef NUMKONG_PARALLEL_H
11
+ #define NUMKONG_PARALLEL_H
12
+
13
+ #include <numkong/types.h> // `nk_size_t`
14
+
15
+ #ifdef __cplusplus
16
+ extern "C" {
17
+ #endif
18
+
19
+ /* Row-tile sizes: 2 kernel tile blocks per chunk, packed blocking 2×2 rows of 16. */
20
+ #define NK_PARALLEL_PACKED_TILE 64
21
+ #define NK_PARALLEL_SYMMETRIC_TILE 32
22
+
23
+ /**
24
+ * @brief Work for one tile. Must be reentrant and confine writes to its own tile.
25
+ * @param tile_index Zero-based index below the `tile_count` passed to the scheduler.
26
+ * @param context Caller-owned state, shared unsynchronized across all tiles.
27
+ */
28
+ typedef void (*nk_tile_body_t)(nk_size_t tile_index, void *context);
29
+
30
+ /** @brief Logical processors available to this process, or 1 when undetectable. */
31
+ nk_size_t nk_parallel_concurrency(void);
32
+
33
+ /**
34
+ * @brief Run @p tile_count independent tiles, at most @p threads of them at a time.
35
+ *
36
+ * Tiles are claimed from a shared counter, giving the dynamic scheduling uneven
37
+ * workloads need. @p threads of 0 means every logical processor, and 1 runs the
38
+ * tiles inline on the calling thread with no pool involved. Returns once every
39
+ * tile has completed. The caller must release the GIL if the bodies do not need it.
40
+ */
41
+ void nk_parallel_for_tiles(nk_size_t tile_count, nk_size_t threads, nk_tile_body_t body, void *context);
42
+
43
+ #ifdef __cplusplus
44
+ }
45
+ #endif
46
+
47
+ #endif // NUMKONG_PARALLEL_H