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.
- package/README.md +1 -1
- package/include/usearch/index.hpp +5 -1
- package/numkong/CITATION.cff +13 -9
- package/numkong/CONTRIBUTING.md +29 -24
- package/numkong/Cargo.toml +1 -1
- package/numkong/README.md +21 -7
- package/numkong/binding.gyp +47 -31
- package/numkong/build.rs +15 -5
- package/numkong/c/dispatch.h +0 -1
- package/numkong/c/parallel.c +139 -0
- package/numkong/c/parallel.h +47 -0
- package/numkong/cmake/toolchain-wasi.cmake +39 -5
- package/numkong/include/README.md +24 -17
- package/numkong/include/numkong/attention/sapphireamx.h +6 -6
- package/numkong/include/numkong/attention/sme.h +2 -2
- package/numkong/include/numkong/capabilities.h +2 -2
- package/numkong/include/numkong/cast/README.md +9 -7
- package/numkong/include/numkong/cast/loongsonasx.h +48 -40
- package/numkong/include/numkong/cast/serial.h +89 -11
- package/numkong/include/numkong/cast.h +3 -3
- package/numkong/include/numkong/curved/README.md +10 -10
- package/numkong/include/numkong/curved/neon.h +1 -0
- package/numkong/include/numkong/curved/neonbfdot.h +1 -0
- package/numkong/include/numkong/curved/smef64.h +1 -0
- package/numkong/include/numkong/dot/README.md +18 -18
- package/numkong/include/numkong/dot/alder.h +8 -8
- package/numkong/include/numkong/dot/haswell.h +8 -8
- package/numkong/include/numkong/dot/icelake.h +12 -12
- package/numkong/include/numkong/dot/loongsonasx.h +6 -6
- package/numkong/include/numkong/dot/neon.h +2 -1
- package/numkong/include/numkong/dot/neonbfdot.h +1 -0
- package/numkong/include/numkong/dot/neonfhm.h +1 -0
- package/numkong/include/numkong/dot/neonsdot.h +1 -0
- package/numkong/include/numkong/dot/powervsx.h +3 -3
- package/numkong/include/numkong/dot/serial.h +19 -6
- package/numkong/include/numkong/dot/skylake.h +4 -4
- package/numkong/include/numkong/dot/svehalf.h +1 -1
- package/numkong/include/numkong/dot/v128relaxed.h +24 -23
- package/numkong/include/numkong/dots/README.md +26 -26
- package/numkong/include/numkong/dots/genoa.h +3 -1
- package/numkong/include/numkong/dots/icelake.h +1 -0
- package/numkong/include/numkong/dots/powervsx.h +1 -1
- package/numkong/include/numkong/dots/serial.h +32 -33
- package/numkong/include/numkong/dots/sierra.h +4 -3
- package/numkong/include/numkong/each/README.md +5 -3
- package/numkong/include/numkong/each/sapphire.h +2 -0
- package/numkong/include/numkong/each/skylake.h +1 -0
- package/numkong/include/numkong/geospatial/README.md +6 -6
- package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
- package/numkong/include/numkong/maxsim/README.md +5 -5
- package/numkong/include/numkong/maxsim/genoa.h +4 -0
- package/numkong/include/numkong/maxsim/icelake.h +1 -0
- package/numkong/include/numkong/maxsim/serial.h +22 -9
- package/numkong/include/numkong/maxsim/sme.h +3 -2
- package/numkong/include/numkong/mesh/README.md +7 -4
- package/numkong/include/numkong/mesh/genoa.h +2 -2
- package/numkong/include/numkong/mesh/haswell.h +8 -8
- package/numkong/include/numkong/mesh/neon.h +6 -6
- package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
- package/numkong/include/numkong/mesh/neonfhm.h +3 -2
- package/numkong/include/numkong/mesh/rvv.h +4 -4
- package/numkong/include/numkong/mesh/serial.h +17 -17
- package/numkong/include/numkong/mesh/skylake.h +4 -4
- package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
- package/numkong/include/numkong/mesh.hpp +2 -2
- package/numkong/include/numkong/numkong.h +1 -1
- package/numkong/include/numkong/numkong.hpp +1 -1
- package/numkong/include/numkong/probability/README.md +17 -15
- package/numkong/include/numkong/probability/skylake.h +1 -1
- package/numkong/include/numkong/reduce/README.md +27 -21
- package/numkong/include/numkong/reduce/genoa.h +2 -1
- package/numkong/include/numkong/reduce/icelake.h +1 -0
- package/numkong/include/numkong/reduce/neon.h +1 -1
- package/numkong/include/numkong/reduce/serial.h +9 -9
- package/numkong/include/numkong/reduce/skylake.h +4 -4
- package/numkong/include/numkong/reduce.hpp +38 -0
- package/numkong/include/numkong/scalar/README.md +29 -28
- package/numkong/include/numkong/set/README.md +17 -14
- package/numkong/include/numkong/set/haswell.h +15 -16
- package/numkong/include/numkong/set/icelake.h +15 -16
- package/numkong/include/numkong/set/loongsonasx.h +12 -10
- package/numkong/include/numkong/set/neon.h +15 -19
- package/numkong/include/numkong/set/powervsx.h +15 -21
- package/numkong/include/numkong/set/serial.h +19 -20
- package/numkong/include/numkong/set/v128relaxed.h +10 -8
- package/numkong/include/numkong/sets/icelake.h +1 -0
- package/numkong/include/numkong/sparse/README.md +4 -4
- package/numkong/include/numkong/sparse/icelake.h +1 -0
- package/numkong/include/numkong/sparse/neon.h +1 -0
- package/numkong/include/numkong/sparse/serial.h +10 -3
- package/numkong/include/numkong/sparse/turin.h +1 -0
- package/numkong/include/numkong/spatial/README.md +25 -24
- package/numkong/include/numkong/spatial/alder.h +2 -1
- package/numkong/include/numkong/spatial/haswell.h +48 -40
- package/numkong/include/numkong/spatial/icelake.h +2 -1
- package/numkong/include/numkong/spatial/neon.h +54 -46
- package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
- package/numkong/include/numkong/spatial/powervsx.h +54 -46
- package/numkong/include/numkong/spatial/serial.h +56 -46
- package/numkong/include/numkong/spatial/sierra.h +4 -3
- package/numkong/include/numkong/spatial/skylake.h +27 -22
- package/numkong/include/numkong/spatial/sve.h +6 -6
- package/numkong/include/numkong/spatial/svebfdot.h +1 -1
- package/numkong/include/numkong/spatial/svehalf.h +1 -1
- package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
- package/numkong/include/numkong/spatials/README.md +15 -15
- package/numkong/include/numkong/spatials/genoa.h +3 -0
- package/numkong/include/numkong/spatials/graniteamx.h +2 -0
- package/numkong/include/numkong/spatials/icelake.h +1 -0
- package/numkong/include/numkong/spatials/sierra.h +1 -0
- package/numkong/include/numkong/tensor.hpp +236 -123
- package/numkong/include/numkong/trigonometry/README.md +4 -4
- package/numkong/include/numkong/trigonometry/serial.h +6 -6
- package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
- package/numkong/include/numkong/types.hpp +46 -16
- package/numkong/include/numkong/vector.hpp +116 -23
- package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
- package/numkong/javascript/README.md +18 -16
- package/numkong/javascript/numkong.c +70 -40
- package/numkong/package.json +8 -8
- package/numkong/rust/README.md +19 -9
- package/numkong/rust/cast.rs +52 -1
- package/numkong/rust/each.rs +480 -7
- package/numkong/rust/matrix.rs +877 -216
- package/numkong/rust/numkong.rs +12 -8
- package/numkong/rust/reduce.rs +433 -5
- package/numkong/rust/tensor.rs +450 -842
- package/numkong/rust/vector.rs +520 -101
- package/numkong/swift/README.md +5 -3
- package/numkong/test/README.md +4 -4
- package/numkong/test/test-wasi.mjs +17 -0
- package/numkong/test/test_each.cpp +30 -1
- package/numkong/test/test_each.py +74 -1
- package/numkong/test/test_reduce.cpp +51 -0
- package/numkong/test/test_tensor.cpp +2 -63
- package/numkong/test/test_tensor.py +93 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
package/README.md
CHANGED
|
@@ -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
|
|
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
|
package/numkong/CITATION.cff
CHANGED
|
@@ -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
|
+
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
package/numkong/CONTRIBUTING.md
CHANGED
|
@@ -32,21 +32,20 @@ build_release/nk_bench
|
|
|
32
32
|
build_release/nk_test
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
| CMake Flag
|
|
36
|
-
|
|
|
37
|
-
| `NK_BUILD_TEST`
|
|
38
|
-
| `NK_BUILD_BENCH`
|
|
39
|
-
| `NK_BUILD_SHARED`
|
|
40
|
-
| `NK_BUILD_SHARED_TEST`
|
|
41
|
-
| `NK_COMPARE_TO_BLAS`
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
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](
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
| RISC-V 64
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
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
|
|
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
|
package/numkong/Cargo.toml
CHANGED
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 |
|
|
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
|
|
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="
|
|
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
|
|
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,
|
|
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.
|
package/numkong/binding.gyp
CHANGED
|
@@ -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
|
|
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
|
-
"-
|
|
141
|
-
"-
|
|
142
|
-
"-
|
|
143
|
-
"-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"-
|
|
147
|
-
"-
|
|
148
|
-
"
|
|
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
|
|
380
|
-
let
|
|
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 -
|
|
387
|
-
|
|
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");
|
package/numkong/c/dispatch.h
CHANGED
|
@@ -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
|