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
|
@@ -38,10 +38,23 @@ set(CMAKE_RANLIB "${WASI_SDK_PATH}/bin/llvm-ranlib${WASI_TOOL_SUFFIX}")
|
|
|
38
38
|
set(CMAKE_SYSROOT "${WASI_SDK_PATH}/share/wasi-sysroot")
|
|
39
39
|
set(CMAKE_FIND_ROOT_PATH "${WASI_SDK_PATH}")
|
|
40
40
|
|
|
41
|
+
# Cross-runtime portable mode: build single-threaded with self-contained memory so the SAME `.wasm`
|
|
42
|
+
# runs under node, wasmtime, AND wasmer (used by the cross-runtime kernel validation). Default OFF keeps
|
|
43
|
+
# the threaded, host-imported-memory build that the full nk_test / nk_bench suites rely on.
|
|
44
|
+
if (NK_WASI_PORTABLE)
|
|
45
|
+
set(NK_WASI_TARGET_ "wasm32-wasip1")
|
|
46
|
+
set(NK_WASI_THREADS_ "")
|
|
47
|
+
set(NK_WASI_MEMORY_ "")
|
|
48
|
+
else ()
|
|
49
|
+
set(NK_WASI_TARGET_ "wasm32-wasip1-threads")
|
|
50
|
+
set(NK_WASI_THREADS_ "-pthread")
|
|
51
|
+
set(NK_WASI_MEMORY_ "-Wl,--import-memory -Wl,--export-memory -Wl,--shared-memory -Wl,--max-memory=2147483648")
|
|
52
|
+
endif ()
|
|
53
|
+
|
|
41
54
|
# WASM SIMD flags (same as Emscripten for consistency).
|
|
42
55
|
set(WASM_SIMD_FLAGS "-msimd128 -mrelaxed-simd")
|
|
43
|
-
set(CMAKE_C_FLAGS_INIT "${WASM_SIMD_FLAGS} --target
|
|
44
|
-
set(CMAKE_CXX_FLAGS_INIT "${WASM_SIMD_FLAGS} --target
|
|
56
|
+
set(CMAKE_C_FLAGS_INIT "${WASM_SIMD_FLAGS} --target=${NK_WASI_TARGET_} ${NK_WASI_THREADS_}")
|
|
57
|
+
set(CMAKE_CXX_FLAGS_INIT "${WASM_SIMD_FLAGS} --target=${NK_WASI_TARGET_} ${NK_WASI_THREADS_} -fno-exceptions")
|
|
45
58
|
|
|
46
59
|
# Optimization flags.
|
|
47
60
|
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG")
|
|
@@ -49,15 +62,18 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
|
|
|
49
62
|
set(CMAKE_C_FLAGS_DEBUG "-O0 -g")
|
|
50
63
|
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
|
|
51
64
|
|
|
52
|
-
# Linker flags for WASI.
|
|
65
|
+
# Linker flags for WASI (the imported/shared-memory group is empty in NK_WASI_PORTABLE mode).
|
|
53
66
|
set(CMAKE_EXE_LINKER_FLAGS_INIT
|
|
54
67
|
"-Wl,--allow-undefined \
|
|
55
|
-
|
|
56
|
-
-Wl,--shared-memory -Wl,--max-memory=2147483648 \
|
|
68
|
+
${NK_WASI_MEMORY_} \
|
|
57
69
|
-Wl,--export=main \
|
|
58
70
|
-Wl,--export=_start"
|
|
59
71
|
)
|
|
60
72
|
|
|
73
|
+
# `wasm-ld` resolves `__global_base`, `__data_end`, and `__heap_base` only in a main module, so a `-shared`
|
|
74
|
+
# WASI build cannot link. Seeded rather than forced, so `-D NK_BUILD_SHARED=ON` still overrides.
|
|
75
|
+
set(NK_BUILD_SHARED OFF CACHE BOOL "Compile a dynamic library")
|
|
76
|
+
|
|
61
77
|
# Do not look for programs in build-host directories.
|
|
62
78
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
|
63
79
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
|
@@ -75,3 +91,21 @@ endif ()
|
|
|
75
91
|
|
|
76
92
|
message(STATUS "NumKong WASI: SIMD128 and Relaxed SIMD enabled")
|
|
77
93
|
message(STATUS "NumKong WASI: Toolchain at ${WASI_SDK_PATH}")
|
|
94
|
+
|
|
95
|
+
# Runtime that CTest invokes on each cross binary, so `ctest` runs the WASI tests cross-engine without a
|
|
96
|
+
# wrapper script. wasmtime and wasmer execute a `.wasm` command directly; node uses the small WASI runner
|
|
97
|
+
# in test/test-wasi.mjs. Relaxed-SIMD lowers differently per engine, so testing more than one matters.
|
|
98
|
+
set(NK_WASM_RUNTIME "wasmtime" CACHE STRING "WASM runtime CTest uses for WASI tests (wasmtime, wasmer, or node)")
|
|
99
|
+
if (NK_WASM_RUNTIME STREQUAL "wasmer")
|
|
100
|
+
find_program(NK_WASMER_EXE_ wasmer PATHS "$ENV{HOME}/.cargo/bin" "$ENV{HOME}/.wasmer/bin")
|
|
101
|
+
set(CMAKE_CROSSCOMPILING_EMULATOR "${NK_WASMER_EXE_};run")
|
|
102
|
+
elseif (NK_WASM_RUNTIME STREQUAL "node")
|
|
103
|
+
find_program(NK_NODE_EXE_ node)
|
|
104
|
+
set(CMAKE_CROSSCOMPILING_EMULATOR
|
|
105
|
+
"${NK_NODE_EXE_};--experimental-wasi-unstable-preview1;${CMAKE_CURRENT_LIST_DIR}/../test/test-wasi.mjs"
|
|
106
|
+
)
|
|
107
|
+
else ()
|
|
108
|
+
find_program(NK_WASMTIME_EXE_ wasmtime PATHS "$ENV{HOME}/.wasmtime/bin")
|
|
109
|
+
set(CMAKE_CROSSCOMPILING_EMULATOR "${NK_WASMTIME_EXE_}")
|
|
110
|
+
endif ()
|
|
111
|
+
message(STATUS "NumKong WASI: CTest runtime = ${NK_WASM_RUNTIME}")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# NumKong for C and C++
|
|
2
2
|
|
|
3
3
|
NumKong's native SDK is the reference surface for the project.
|
|
4
|
-
The plain C ABI exposes every kernel family directly: dot products, dense distances, binary metrics, probability divergences, geospatial solvers, curved-space kernels, sparse intersections, mesh alignment, packed matrix multiplication, symmetric self-similarity,
|
|
4
|
+
The plain C ABI exposes every kernel family directly: dot products, dense distances, binary metrics, probability divergences, geospatial solvers, curved-space kernels, sparse intersections, mesh alignment, packed matrix multiplication, symmetric self-similarity, late-interaction scoring, elementwise arithmetic, reductions, type conversions, scalar math, and trigonometry.
|
|
5
5
|
The ABI is stable, versioned, and callable from any language that can load a shared library.
|
|
6
6
|
There is no runtime overhead: no hidden thread pool, no implicit allocation, no garbage collector interaction.
|
|
7
7
|
The C++ layer stays thin, typed, allocator-aware, and close enough to inline through, adding type-level result promotion and owning containers without hiding the dispatch model or the mixed-precision policy.
|
|
@@ -43,7 +43,7 @@ Packing handles internal layout itself and does not require caller-side alignmen
|
|
|
43
43
|
|
|
44
44
|
## Ecosystem Comparison
|
|
45
45
|
|
|
46
|
-
| Feature | NumKong | [OpenBLAS]
|
|
46
|
+
| Feature | NumKong | [OpenBLAS][openblas] | [Eigen][eigen] |
|
|
47
47
|
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
48
48
|
| Operation families | dots, distances, binary, probability, geospatial, curved, mesh, sparse, MaxSim, elementwise, reductions, cast, trig | dense linear algebra only | dense LA, some reductions and elementwise |
|
|
49
49
|
| Precision | Sub-byte to Float64 dtypes; automatic widening per scalar type; Kahan-compensated summation; 0 ULP Float32/Float64 where applicable | Float32, Float64 only; same-type in/out; no compensated summation | Float16/BFloat16 partial; no Float8 or sub-byte; manual casts; no compensated summation |
|
|
@@ -52,6 +52,8 @@ Packing handles internal layout itself and does not require caller-side alignmen
|
|
|
52
52
|
| Symmetric kernels, SYRK-like | skips duplicate pairs, up to 2x speedup for self-distance | `SSYRK`/`DSYRK` for rank-k updates | `.selfadjointView` for rank-k updates |
|
|
53
53
|
| Memory model | Caller-owned buffers; C++ adds `tensor<T,A>` with per-container allocators | Caller-managed buffers; no container abstraction | Lazy expression templates avoid most temporaries; `aligned_allocator` provided |
|
|
54
54
|
|
|
55
|
+
[openblas]: https://github.com/OpenMathLib/OpenBLAS
|
|
56
|
+
[eigen]: https://gitlab.com/libeigen/eigen
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
## Installation
|
|
@@ -168,7 +170,7 @@ The higher-level templates use `result_type_ = typename in_type_::dot_result_t`
|
|
|
168
170
|
The fast typed overloads are constrained so that overriding the result type away from the native policy can disable the specialized path and fall back to the more generic one.
|
|
169
171
|
|
|
170
172
|
When `__cpp_lib_format >= 202110L` for the C++23 `<format>` header support, all NumKong scalar types provide `std::formatter` specializations with similar format specs to the traditional `float`.
|
|
171
|
-
For the
|
|
173
|
+
For the Float16 type, the output for `nk::f16_t::from_f32(3.14f)` will look like:
|
|
172
174
|
|
|
173
175
|
| Format spec | Output example | Description |
|
|
174
176
|
| ----------- | -------------------- | -------------------------------------- |
|
|
@@ -189,10 +191,10 @@ They include real, complex, packed-binary, mini-float, and mixed-precision forms
|
|
|
189
191
|
```c
|
|
190
192
|
nk_f32c_t a[384];
|
|
191
193
|
nk_f32c_t b[384];
|
|
192
|
-
|
|
194
|
+
nk_f64c_t out = {0, 0}; // widened f32c → f64c output
|
|
193
195
|
|
|
194
|
-
nk_dot_f32c(a, b, 384, out);
|
|
195
|
-
nk_vdot_f32c(a, b, 384, out);
|
|
196
|
+
nk_dot_f32c(a, b, 384, &out); // complex inner product
|
|
197
|
+
nk_vdot_f32c(a, b, 384, &out); // conjugated variant, like numpy.vdot
|
|
196
198
|
```
|
|
197
199
|
|
|
198
200
|
For quantized retrieval pipelines, the storage format often matters more than the nominal math family.
|
|
@@ -231,15 +233,18 @@ nk_hamming_u1(a, b, 128 * 8, &hamming);
|
|
|
231
233
|
nk_jaccard_u1(a, b, 128 * 8, &jaccard);
|
|
232
234
|
```
|
|
233
235
|
|
|
234
|
-
|
|
236
|
+
`nk_jaccard_u32` is a dense word-wise kernel rather than a sorted-set operation.
|
|
237
|
+
It walks two arrays of `n` 32-bit words position by position and returns `1 - matches / n`.
|
|
235
238
|
|
|
236
239
|
```c
|
|
237
|
-
nk_u32_t
|
|
238
|
-
nk_f32_t
|
|
239
|
-
nk_jaccard_u32(
|
|
240
|
-
assert(
|
|
240
|
+
nk_u32_t a[] = {1, 3, 5, 7, 9}, b[] = {1, 3, 5, 8, 10};
|
|
241
|
+
nk_f32_t jaccard_words = 0;
|
|
242
|
+
nk_jaccard_u32(a, b, 5, &jaccard_words); // 1 - matches / n
|
|
243
|
+
assert(jaccard_words > 0.0f && jaccard_words < 1.0f && "3 of 5 words match");
|
|
241
244
|
```
|
|
242
245
|
|
|
246
|
+
Sorted arrays of integer identifiers are handled by the sparse kernels instead, described below.
|
|
247
|
+
|
|
243
248
|
## Probability Metrics
|
|
244
249
|
|
|
245
250
|
Probability kernels target divergences directly instead of making you rebuild them from scalar loops.
|
|
@@ -351,8 +356,8 @@ You can also use a more traditional syntax with member functions, also leveragin
|
|
|
351
356
|
Similar to NumPy, but statically typed:
|
|
352
357
|
|
|
353
358
|
```cpp
|
|
354
|
-
auto
|
|
355
|
-
auto minimum_index = nk::argmin(
|
|
359
|
+
auto second_column = t[all, 1, slice]; // strided column view → {2, 5, 8}
|
|
360
|
+
auto minimum_index = nk::argmin(second_column); // index of the minimum in the second column
|
|
356
361
|
```
|
|
357
362
|
|
|
358
363
|
The view types are conceptually close to `std::mdspan` from C++23.
|
|
@@ -498,10 +503,11 @@ The native API covers `rmsd`, `kabsch`, and `umeyama`.
|
|
|
498
503
|
nk_f32_t source[] = {0, 0, 0, 1, 0, 0, 0, 1, 0};
|
|
499
504
|
nk_f32_t target[] = {0, 0, 0, 2, 0, 0, 0, 2, 0};
|
|
500
505
|
nk_f32_t a_centroid[3], b_centroid[3], rotation[9];
|
|
501
|
-
nk_f32_t scale = 0
|
|
506
|
+
nk_f32_t scale = 0;
|
|
507
|
+
nk_f64_t rmsd = 0; // widened f32 → f64 output
|
|
502
508
|
|
|
503
509
|
nk_umeyama_f32(source, target, 3, a_centroid, b_centroid, rotation, &scale, &rmsd);
|
|
504
|
-
assert(rmsd < 1e-
|
|
510
|
+
assert(rmsd < 1e-6 && "umeyama should recover exact alignment");
|
|
505
511
|
assert(scale > 1.99f && scale < 2.01f && "umeyama should recover 2x scale");
|
|
506
512
|
```
|
|
507
513
|
|
|
@@ -592,13 +598,14 @@ The main user-facing CMake options are:
|
|
|
592
598
|
- `NK_BUILD_SHARED` builds a shared library, ON by default for standalone builds and OFF when included as a subdirectory.
|
|
593
599
|
- `NK_BUILD_TEST` and `NK_BUILD_BENCH` enable precision tests and benchmarks respectively, both OFF by default.
|
|
594
600
|
- `NK_DYNAMIC_DISPATCH=1` compiles all backends into one binary and selects at runtime via `nk_capabilities()`, recommended for shipping one binary across CPU generations.
|
|
601
|
+
It is a preprocessor definition rather than a CMake option, so pass it through the compiler flags.
|
|
595
602
|
- `NK_COMPARE_TO_BLAS` and `NK_COMPARE_TO_MKL` link benchmarks against a system BLAS or Intel MKL, each accepting `AUTO`, `ON`, or `OFF` with `AUTO` as the default.
|
|
596
603
|
|
|
597
|
-
The build enforces C99 for the C layer and C++
|
|
604
|
+
The build enforces C99 for the C layer and C++20 for the C++ layer.
|
|
598
605
|
|
|
599
606
|
```sh
|
|
600
607
|
cmake -B build -D CMAKE_BUILD_TYPE=Release -D NK_BUILD_TEST=ON
|
|
601
|
-
cmake -B build -D
|
|
608
|
+
cmake -B build -D CMAKE_C_FLAGS="-DNK_DYNAMIC_DISPATCH=1" -D NK_BUILD_BENCH=ON -D NK_COMPARE_TO_MKL=ON
|
|
602
609
|
```
|
|
603
610
|
|
|
604
611
|
## Cross-Compilation
|
|
@@ -117,7 +117,7 @@ typedef struct {
|
|
|
117
117
|
* 2. Polynomial approximation: exp(r) ≈ 1 + r + r²/2 + ... (degree 6)
|
|
118
118
|
* 3. Reconstruction: exp(x) = 2ⁿ × exp(r)
|
|
119
119
|
*
|
|
120
|
-
* @param
|
|
120
|
+
* @param x_f32x16 Input vector (16 floats)
|
|
121
121
|
* @return exp(x) for each element
|
|
122
122
|
*/
|
|
123
123
|
NK_INTERNAL __m512 nk_exp_ps_avx512_(__m512 x_f32x16) {
|
|
@@ -169,7 +169,7 @@ NK_INTERNAL __m512 nk_exp_ps_avx512_(__m512 x_f32x16) {
|
|
|
169
169
|
*
|
|
170
170
|
* Performance: ~12-15 cycles for 16 floats (vs ~18-22 for degree-6)
|
|
171
171
|
*
|
|
172
|
-
* @param
|
|
172
|
+
* @param x_f32x16 Input vector (16 floats)
|
|
173
173
|
* @return exp(x) approximation
|
|
174
174
|
*/
|
|
175
175
|
NK_INTERNAL __m512 nk_exp_ps_fast_avx512_(__m512 x_f32x16) {
|
|
@@ -475,10 +475,10 @@ NK_INTERNAL void nk_attention_softmax_update_(nk_attention_softmax_row_state_t *
|
|
|
475
475
|
* When processing a new KV block with larger scores, previous O accumulator
|
|
476
476
|
* needs rescaling: O = O × exp(oldₘₐₓ - newₘₐₓ)
|
|
477
477
|
*
|
|
478
|
-
* @param output
|
|
479
|
-
* @param head_dim
|
|
480
|
-
* @param
|
|
481
|
-
* @param
|
|
478
|
+
* @param output Output accumulator [16][head_dim] in F32
|
|
479
|
+
* @param head_dim Head dimension
|
|
480
|
+
* @param old_max_f32x16 Previous running max per row (16 values)
|
|
481
|
+
* @param new_max_f32x16 New running max per row (16 values)
|
|
482
482
|
*/
|
|
483
483
|
NK_INTERNAL void nk_attention_rescale_output_(nk_f32_t *output, nk_size_t head_dim, __m512 old_max_f32x16,
|
|
484
484
|
__m512 new_max_f32x16) {
|
|
@@ -162,8 +162,8 @@ typedef struct {
|
|
|
162
162
|
* Uses Cody-Waite range reduction + Horner polynomial (degree 4).
|
|
163
163
|
* Accuracy: ~0.1% relative error, acceptable for softmax normalization.
|
|
164
164
|
*
|
|
165
|
-
* @param
|
|
166
|
-
* @param
|
|
165
|
+
* @param predicate_b32x Active predicate
|
|
166
|
+
* @param x_f32x Input vector
|
|
167
167
|
* @return exp(x) approximation
|
|
168
168
|
*/
|
|
169
169
|
NK_INTERNAL svfloat32_t nk_exp_f32_sve_(svbool_t predicate_b32x, svfloat32_t x_f32x) __arm_streaming {
|
|
@@ -95,8 +95,8 @@
|
|
|
95
95
|
#include "numkong/types.h" // `nk_u64_t`, `NK_DEFINED_LINUX_`
|
|
96
96
|
|
|
97
97
|
#define NK_VERSION_MAJOR 7
|
|
98
|
-
#define NK_VERSION_MINOR
|
|
99
|
-
#define NK_VERSION_PATCH
|
|
98
|
+
#define NK_VERSION_MINOR 8
|
|
99
|
+
#define NK_VERSION_PATCH 1
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* @brief Removes compile-time dispatching, and replaces it with runtime dispatching.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Type Conversions in NumKong
|
|
2
2
|
|
|
3
|
-
NumKong implements bidirectional type conversions between all supported numeric formats through
|
|
4
|
-
|
|
3
|
+
NumKong implements bidirectional type conversions between all supported numeric formats through a wider hub type: `u64` when both sides are unsigned integers, `i64` when both sides are integers and at least one is signed, and `f64c` for everything else.
|
|
4
|
+
The SIMD backends use a narrower Float32 hub for the subset of types they cover.
|
|
5
|
+
Conversions cover IEEE 754 floats (Float16, Float32, Float64), brain float (BFloat16), Float8 formats (e4m3, e5m2, e2m3, e3m2), and integers (Int8–Int64, UInt8–UInt64, packed i4x2/u4x2, packed u1x8).
|
|
5
6
|
All conversions use round-to-nearest-even (RNE) for narrowing and exact widening where the target format has sufficient range and precision.
|
|
6
7
|
|
|
7
8
|
BFloat16 relates to Float32 by truncation with rounding:
|
|
@@ -77,16 +78,17 @@ Packed sub-byte conversions:
|
|
|
77
78
|
|
|
78
79
|
### Lookup Tables for Mini-Floats
|
|
79
80
|
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
`nk_e5m2_to_f32_serial` indexes a 128-entry table with the 7-bit magnitude, and `nk_e2m3_to_f32_serial` and `nk_e3m2_to_f32_serial` index 32-entry tables with their 5-bit magnitudes, OR-ing the sign bit back in afterwards.
|
|
82
|
+
`nk_e4m3_to_f32_serial` needs no table: the exponent and mantissa fields map onto Float32 with a bias shift of 120, and only the subnormal and the single NaN encoding are special-cased.
|
|
83
|
+
The reverse direction (`nk_f32_to_e4m3_serial`) is bit manipulation with RNE rounding: NaN and infinity are mapped explicitly, subnormals below 2⁻⁶ are rounded through a `× 512` scale, and normals get their 23-bit mantissa rounded down to 3 bits.
|
|
84
|
+
The SIMD backends do not gather from these tables.
|
|
85
|
+
On x86 the Float8 paths ride the F16C converters described below, and NEON looks up Float16 high bytes with `VQTBL4` over a 128-byte table in `nk_e4m3x16_to_f16x8x2_neon_` and its siblings.
|
|
84
86
|
|
|
85
87
|
### BFloat16 as Truncated Float32
|
|
86
88
|
|
|
87
89
|
`nk_bf16_to_f32_serial` zero-extends by left-shifting 16 bits — exact, no rounding error, single-cycle on all platforms.
|
|
88
90
|
`nk_f32_to_bf16_serial` right-shifts with round-to-nearest-even: adds a rounding bias of `0x7FFF + ((bits >> 16) & 1)` before truncating, matching the IEEE 754 RNE tie-breaking rule.
|
|
89
|
-
NEON backend uses `
|
|
91
|
+
NEON backend uses `vmovl_u16` + `vshlq_n_u32` for zero-extension; Haswell uses `VPSLLD` / `VPSRLD` shifts.
|
|
90
92
|
|
|
91
93
|
### F16C Hardware Conversion
|
|
92
94
|
|
|
@@ -139,106 +139,114 @@ NK_INTERNAL __m128 nk_sqrt_f32x4_loongsonasx_(__m128 x_f32x4) {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/** @brief Angular from_dot: computes 1 − dot × rsqrt(query_sumsq × target_sumsq) for 4 pairs (LSX 128-bit f32). */
|
|
142
|
-
NK_INTERNAL void nk_angular_through_f32_from_dot_loongsonasx_(nk_b128_vec_t
|
|
143
|
-
nk_b128_vec_t
|
|
144
|
-
|
|
142
|
+
NK_INTERNAL void nk_angular_through_f32_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_f32_t query_sumsq,
|
|
143
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
144
|
+
nk_b128_vec_t *result_vec) {
|
|
145
|
+
__m128 dots_f32x4 = dots_vec->xmm_ps;
|
|
145
146
|
__m128 query_sumsq_f32x4 = nk_xvreplgr2vr_s_128_(query_sumsq);
|
|
146
|
-
__m128 products_f32x4 = __lsx_vfmul_s(query_sumsq_f32x4,
|
|
147
|
+
__m128 products_f32x4 = __lsx_vfmul_s(query_sumsq_f32x4, target_sumsqs_vec->xmm_ps);
|
|
147
148
|
__m128 rsqrt_f32x4 = __lsx_vfrsqrt_s(products_f32x4);
|
|
148
149
|
__m128 normalized_f32x4 = __lsx_vfmul_s(dots_f32x4, rsqrt_f32x4);
|
|
149
150
|
__m128 one_f32x4 = nk_xvreplgr2vr_s_128_(1.0f);
|
|
150
151
|
__m128 angular_f32x4 = __lsx_vfsub_s(one_f32x4, normalized_f32x4);
|
|
151
152
|
__m128 zero_f32x4 = (__m128)__lsx_vreplgr2vr_w(0);
|
|
152
|
-
|
|
153
|
+
result_vec->xmm_ps = __lsx_vfmax_s(angular_f32x4, zero_f32x4);
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
/** @brief Euclidean from_dot: computes √(query_sumsq + target_sumsq − 2 × dot) for 4 pairs (LSX 128-bit f32). */
|
|
156
|
-
NK_INTERNAL void nk_euclidean_through_f32_from_dot_loongsonasx_(nk_b128_vec_t
|
|
157
|
-
nk_b128_vec_t
|
|
158
|
-
|
|
157
|
+
NK_INTERNAL void nk_euclidean_through_f32_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_f32_t query_sumsq,
|
|
158
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
159
|
+
nk_b128_vec_t *result_vec) {
|
|
160
|
+
__m128 dots_f32x4 = dots_vec->xmm_ps;
|
|
159
161
|
__m128 query_sumsq_f32x4 = nk_xvreplgr2vr_s_128_(query_sumsq);
|
|
160
|
-
__m128 sum_sq_f32x4 = __lsx_vfadd_s(query_sumsq_f32x4,
|
|
162
|
+
__m128 sum_sq_f32x4 = __lsx_vfadd_s(query_sumsq_f32x4, target_sumsqs_vec->xmm_ps);
|
|
161
163
|
__m128 two_f32x4 = nk_xvreplgr2vr_s_128_(2.0f);
|
|
162
164
|
// dist_sq = sum_sq − 2 × dots = -(2 × dots − sum_sq)
|
|
163
165
|
__m128 dist_sq_f32x4 = __lsx_vfnmsub_s(two_f32x4, dots_f32x4, sum_sq_f32x4);
|
|
164
|
-
|
|
166
|
+
result_vec->xmm_ps = nk_sqrt_f32x4_loongsonasx_(dist_sq_f32x4);
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
/** @brief Angular from_dot for native f64: 1 − dot / √(query_sumsq × target_sumsq) for 4 pairs (LASX 256-bit). */
|
|
168
|
-
NK_INTERNAL void nk_angular_through_f64_from_dot_loongsonasx_(nk_b256_vec_t
|
|
169
|
-
nk_b256_vec_t
|
|
170
|
-
|
|
170
|
+
NK_INTERNAL void nk_angular_through_f64_from_dot_loongsonasx_(nk_b256_vec_t const *dots_vec, nk_f64_t query_sumsq,
|
|
171
|
+
nk_b256_vec_t const *target_sumsqs_vec,
|
|
172
|
+
nk_b256_vec_t *result_vec) {
|
|
173
|
+
__m256d dots_f64x4 = dots_vec->ymm_pd;
|
|
171
174
|
__m256d query_sumsq_f64x4 = nk_xvfreplgr2vr_d_(query_sumsq);
|
|
172
|
-
__m256d products_f64x4 = __lasx_xvfmul_d(query_sumsq_f64x4,
|
|
175
|
+
__m256d products_f64x4 = __lasx_xvfmul_d(query_sumsq_f64x4, target_sumsqs_vec->ymm_pd);
|
|
173
176
|
__m256d sqrt_products_f64x4 = __lasx_xvfsqrt_d(products_f64x4);
|
|
174
177
|
__m256d normalized_f64x4 = __lasx_xvfdiv_d(dots_f64x4, sqrt_products_f64x4);
|
|
175
178
|
__m256d one_f64x4 = nk_xvfreplgr2vr_d_(1.0);
|
|
176
179
|
__m256d angular_f64x4 = __lasx_xvfsub_d(one_f64x4, normalized_f64x4);
|
|
177
180
|
__m256d zero_f64x4 = (__m256d)__lasx_xvreplgr2vr_d(0);
|
|
178
|
-
|
|
181
|
+
result_vec->ymm_pd = __lasx_xvfmax_d(angular_f64x4, zero_f64x4);
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
/** @brief Euclidean from_dot for native f64: √(query_sumsq + target_sumsq − 2 × dot) for 4 pairs (LASX 256-bit). */
|
|
182
|
-
NK_INTERNAL void nk_euclidean_through_f64_from_dot_loongsonasx_(nk_b256_vec_t
|
|
183
|
-
nk_b256_vec_t
|
|
184
|
-
|
|
185
|
+
NK_INTERNAL void nk_euclidean_through_f64_from_dot_loongsonasx_(nk_b256_vec_t const *dots_vec, nk_f64_t query_sumsq,
|
|
186
|
+
nk_b256_vec_t const *target_sumsqs_vec,
|
|
187
|
+
nk_b256_vec_t *result_vec) {
|
|
188
|
+
__m256d dots_f64x4 = dots_vec->ymm_pd;
|
|
185
189
|
__m256d query_sumsq_f64x4 = nk_xvfreplgr2vr_d_(query_sumsq);
|
|
186
|
-
__m256d sum_sq_f64x4 = __lasx_xvfadd_d(query_sumsq_f64x4,
|
|
190
|
+
__m256d sum_sq_f64x4 = __lasx_xvfadd_d(query_sumsq_f64x4, target_sumsqs_vec->ymm_pd);
|
|
187
191
|
__m256d two_f64x4 = nk_xvfreplgr2vr_d_(2.0);
|
|
188
192
|
// dist_sq = sum_sq − 2 × dots = -(2 × dots − sum_sq)
|
|
189
193
|
__m256d dist_sq_f64x4 = __lasx_xvfnmsub_d(two_f64x4, dots_f64x4, sum_sq_f64x4);
|
|
190
194
|
__m256d zero_f64x4 = (__m256d)__lasx_xvreplgr2vr_d(0);
|
|
191
|
-
|
|
195
|
+
result_vec->ymm_pd = __lasx_xvfsqrt_d(__lasx_xvfmax_d(dist_sq_f64x4, zero_f64x4));
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
/** @brief Angular from_dot for i32 accumulators: cast i32 → f32, rsqrt+NR, clamp. 4 pairs (LSX 128-bit). */
|
|
195
|
-
NK_INTERNAL void nk_angular_through_i32_from_dot_loongsonasx_(nk_b128_vec_t
|
|
196
|
-
nk_b128_vec_t
|
|
197
|
-
|
|
199
|
+
NK_INTERNAL void nk_angular_through_i32_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_i32_t query_sumsq,
|
|
200
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
201
|
+
nk_b128_vec_t *result_vec) {
|
|
202
|
+
__m128 dots_f32x4 = __lsx_vffint_s_w(dots_vec->xmm);
|
|
198
203
|
__m128 query_sumsq_f32x4 = nk_xvreplgr2vr_s_128_((nk_f32_t)query_sumsq);
|
|
199
|
-
__m128 products_f32x4 = __lsx_vfmul_s(query_sumsq_f32x4, __lsx_vffint_s_w(
|
|
204
|
+
__m128 products_f32x4 = __lsx_vfmul_s(query_sumsq_f32x4, __lsx_vffint_s_w(target_sumsqs_vec->xmm));
|
|
200
205
|
__m128 rsqrt_f32x4 = __lsx_vfrsqrt_s(products_f32x4);
|
|
201
206
|
__m128 normalized_f32x4 = __lsx_vfmul_s(dots_f32x4, rsqrt_f32x4);
|
|
202
207
|
__m128 one_f32x4 = nk_xvreplgr2vr_s_128_(1.0f);
|
|
203
208
|
__m128 angular_f32x4 = __lsx_vfsub_s(one_f32x4, normalized_f32x4);
|
|
204
209
|
__m128 zero_f32x4 = (__m128)__lsx_vreplgr2vr_w(0);
|
|
205
|
-
|
|
210
|
+
result_vec->xmm_ps = __lsx_vfmax_s(angular_f32x4, zero_f32x4);
|
|
206
211
|
}
|
|
207
212
|
|
|
208
213
|
/** @brief Euclidean from_dot for i32 accumulators: cast i32 → f32, then √(a² + b² − 2ab). 4 pairs (LSX 128-bit). */
|
|
209
|
-
NK_INTERNAL void nk_euclidean_through_i32_from_dot_loongsonasx_(nk_b128_vec_t
|
|
210
|
-
nk_b128_vec_t
|
|
211
|
-
|
|
214
|
+
NK_INTERNAL void nk_euclidean_through_i32_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_i32_t query_sumsq,
|
|
215
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
216
|
+
nk_b128_vec_t *result_vec) {
|
|
217
|
+
__m128 dots_f32x4 = __lsx_vffint_s_w(dots_vec->xmm);
|
|
212
218
|
__m128 query_sumsq_f32x4 = nk_xvreplgr2vr_s_128_((nk_f32_t)query_sumsq);
|
|
213
|
-
__m128 sum_sq_f32x4 = __lsx_vfadd_s(query_sumsq_f32x4, __lsx_vffint_s_w(
|
|
219
|
+
__m128 sum_sq_f32x4 = __lsx_vfadd_s(query_sumsq_f32x4, __lsx_vffint_s_w(target_sumsqs_vec->xmm));
|
|
214
220
|
__m128 two_f32x4 = nk_xvreplgr2vr_s_128_(2.0f);
|
|
215
221
|
__m128 dist_sq_f32x4 = __lsx_vfnmsub_s(two_f32x4, dots_f32x4, sum_sq_f32x4);
|
|
216
|
-
|
|
222
|
+
result_vec->xmm_ps = nk_sqrt_f32x4_loongsonasx_(dist_sq_f32x4);
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
/** @brief Angular from_dot for u32 accumulators: cast u32 → f32, rsqrt+NR, clamp. 4 pairs (LSX 128-bit). */
|
|
220
|
-
NK_INTERNAL void nk_angular_through_u32_from_dot_loongsonasx_(nk_b128_vec_t
|
|
221
|
-
nk_b128_vec_t
|
|
222
|
-
|
|
226
|
+
NK_INTERNAL void nk_angular_through_u32_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_u32_t query_sumsq,
|
|
227
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
228
|
+
nk_b128_vec_t *result_vec) {
|
|
229
|
+
__m128 dots_f32x4 = __lsx_vffint_s_w(dots_vec->xmm);
|
|
223
230
|
__m128 query_sumsq_f32x4 = nk_xvreplgr2vr_s_128_((nk_f32_t)query_sumsq);
|
|
224
|
-
__m128 products_f32x4 = __lsx_vfmul_s(query_sumsq_f32x4, __lsx_vffint_s_w(
|
|
231
|
+
__m128 products_f32x4 = __lsx_vfmul_s(query_sumsq_f32x4, __lsx_vffint_s_w(target_sumsqs_vec->xmm));
|
|
225
232
|
__m128 rsqrt_f32x4 = __lsx_vfrsqrt_s(products_f32x4);
|
|
226
233
|
__m128 normalized_f32x4 = __lsx_vfmul_s(dots_f32x4, rsqrt_f32x4);
|
|
227
234
|
__m128 one_f32x4 = nk_xvreplgr2vr_s_128_(1.0f);
|
|
228
235
|
__m128 angular_f32x4 = __lsx_vfsub_s(one_f32x4, normalized_f32x4);
|
|
229
236
|
__m128 zero_f32x4 = (__m128)__lsx_vreplgr2vr_w(0);
|
|
230
|
-
|
|
237
|
+
result_vec->xmm_ps = __lsx_vfmax_s(angular_f32x4, zero_f32x4);
|
|
231
238
|
}
|
|
232
239
|
|
|
233
240
|
/** @brief Euclidean from_dot for u32 accumulators: cast u32 → f32, then √(a² + b² − 2ab). 4 pairs (LSX 128-bit). */
|
|
234
|
-
NK_INTERNAL void nk_euclidean_through_u32_from_dot_loongsonasx_(nk_b128_vec_t
|
|
235
|
-
nk_b128_vec_t
|
|
236
|
-
|
|
241
|
+
NK_INTERNAL void nk_euclidean_through_u32_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_u32_t query_sumsq,
|
|
242
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
243
|
+
nk_b128_vec_t *result_vec) {
|
|
244
|
+
__m128 dots_f32x4 = __lsx_vffint_s_w(dots_vec->xmm);
|
|
237
245
|
__m128 query_sumsq_f32x4 = nk_xvreplgr2vr_s_128_((nk_f32_t)query_sumsq);
|
|
238
|
-
__m128 sum_sq_f32x4 = __lsx_vfadd_s(query_sumsq_f32x4, __lsx_vffint_s_w(
|
|
246
|
+
__m128 sum_sq_f32x4 = __lsx_vfadd_s(query_sumsq_f32x4, __lsx_vffint_s_w(target_sumsqs_vec->xmm));
|
|
239
247
|
__m128 two_f32x4 = nk_xvreplgr2vr_s_128_(2.0f);
|
|
240
248
|
__m128 dist_sq_f32x4 = __lsx_vfnmsub_s(two_f32x4, dots_f32x4, sum_sq_f32x4);
|
|
241
|
-
|
|
249
|
+
result_vec->xmm_ps = nk_sqrt_f32x4_loongsonasx_(dist_sq_f32x4);
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
#pragma endregion Vectorized From Dot Helpers
|
|
@@ -13,17 +13,6 @@
|
|
|
13
13
|
extern "C" {
|
|
14
14
|
#endif
|
|
15
15
|
|
|
16
|
-
/* Keep the serial conversions below actually scalar, regardless of build type.
|
|
17
|
-
* Without this, -O3 + LTO can vectorize or clone the serial kernels under AVX-512
|
|
18
|
-
* callers in dispatch_*.c, which wastes binary and breaks the nk_*_serial-as-scalar-oracle
|
|
19
|
-
* contract. See dots/serial.h. */
|
|
20
|
-
#if defined(__clang__)
|
|
21
|
-
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
22
|
-
#elif defined(__GNUC__)
|
|
23
|
-
#pragma GCC push_options
|
|
24
|
-
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
25
|
-
#endif
|
|
26
|
-
|
|
27
16
|
#pragma region Type Punned Loads and Stores
|
|
28
17
|
|
|
29
18
|
/** @brief Type-agnostic 32-bit full load (scalar). */
|
|
@@ -563,6 +552,17 @@ NK_INTERNAL void nk_strided_load_b8x16_serial_(void const *src, nk_size_t stride
|
|
|
563
552
|
|
|
564
553
|
#pragma endregion Type Punned Loads and Stores
|
|
565
554
|
|
|
555
|
+
/* Keep the serial conversions below actually scalar, regardless of build type.
|
|
556
|
+
* Without this, -O3 + LTO can vectorize or clone the serial kernels under AVX-512
|
|
557
|
+
* callers in dispatch_*.c, which wastes binary and breaks the nk_*_serial-as-scalar-oracle
|
|
558
|
+
* contract. See dots/serial.h. */
|
|
559
|
+
#if defined(__clang__)
|
|
560
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
561
|
+
#elif defined(__GNUC__)
|
|
562
|
+
#pragma GCC push_options
|
|
563
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
564
|
+
#endif
|
|
565
|
+
|
|
566
566
|
/**
|
|
567
567
|
* @brief Expands an `f16` (IEEE-754 16-bit) to a `float`.
|
|
568
568
|
*
|
|
@@ -622,6 +622,12 @@ NK_PUBLIC void nk_f16_to_f32_serial(nk_f16_t const *src, nk_f32_t *dest) {
|
|
|
622
622
|
#endif
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
+
#if defined(__clang__)
|
|
626
|
+
#pragma clang attribute pop
|
|
627
|
+
#elif defined(__GNUC__)
|
|
628
|
+
#pragma GCC pop_options
|
|
629
|
+
#endif
|
|
630
|
+
|
|
625
631
|
/** @brief Load 4 × f16 from memory and upcast them to 4 × f32. */
|
|
626
632
|
NK_INTERNAL void nk_load_f16x4_to_f32x4_serial_(void const *src, nk_b128_vec_t *dst) {
|
|
627
633
|
nk_f16_t const *scalars = (nk_f16_t const *)src;
|
|
@@ -644,6 +650,13 @@ NK_INTERNAL void nk_partial_load_f16x4_to_f32x4_serial_(nk_f16_t const *src, nk_
|
|
|
644
650
|
}
|
|
645
651
|
}
|
|
646
652
|
|
|
653
|
+
#if defined(__clang__)
|
|
654
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
655
|
+
#elif defined(__GNUC__)
|
|
656
|
+
#pragma GCC push_options
|
|
657
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
658
|
+
#endif
|
|
659
|
+
|
|
647
660
|
/**
|
|
648
661
|
* @brief Compresses a `float` to an `f16` (IEEE-754 16-bit).
|
|
649
662
|
*
|
|
@@ -946,6 +959,12 @@ NK_PUBLIC void nk_f32_to_e4m3_serial(nk_f32_t const *src, nk_e4m3_t *dest) {
|
|
|
946
959
|
*dest = (nk_e4m3_t)(sign | (exp_field << 3) | mant_field);
|
|
947
960
|
}
|
|
948
961
|
|
|
962
|
+
#if defined(__clang__)
|
|
963
|
+
#pragma clang attribute pop
|
|
964
|
+
#elif defined(__GNUC__)
|
|
965
|
+
#pragma GCC pop_options
|
|
966
|
+
#endif
|
|
967
|
+
|
|
949
968
|
/**
|
|
950
969
|
* @brief Convert FP8 E4M3 to IEEE 754 half-precision float.
|
|
951
970
|
*
|
|
@@ -1039,6 +1058,13 @@ NK_INTERNAL void nk_e5m2_to_f32_manual_(nk_e5m2_t const *src, nk_f32_t *dest) {
|
|
|
1039
1058
|
*dest = conv.f;
|
|
1040
1059
|
}
|
|
1041
1060
|
|
|
1061
|
+
#if defined(__clang__)
|
|
1062
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
1063
|
+
#elif defined(__GNUC__)
|
|
1064
|
+
#pragma GCC push_options
|
|
1065
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
1066
|
+
#endif
|
|
1067
|
+
|
|
1042
1068
|
NK_PUBLIC void nk_e5m2_to_f32_serial(nk_e5m2_t const *src, nk_f32_t *dest) {
|
|
1043
1069
|
static nk_u32_t const lut[128] = {
|
|
1044
1070
|
0x00000000, 0x37800000, 0x38000000, 0x38400000, // exp=0 sub
|
|
@@ -1181,6 +1207,12 @@ NK_PUBLIC void nk_f32_to_e5m2_serial(nk_f32_t const *src, nk_e5m2_t *dest) {
|
|
|
1181
1207
|
*dest = (nk_e5m2_t)(sign | (exp_field << 2) | mant_field);
|
|
1182
1208
|
}
|
|
1183
1209
|
|
|
1210
|
+
#if defined(__clang__)
|
|
1211
|
+
#pragma clang attribute pop
|
|
1212
|
+
#elif defined(__GNUC__)
|
|
1213
|
+
#pragma GCC pop_options
|
|
1214
|
+
#endif
|
|
1215
|
+
|
|
1184
1216
|
/**
|
|
1185
1217
|
* @brief Convert FP8 E5M2 to IEEE 754 half-precision float.
|
|
1186
1218
|
*
|
|
@@ -1315,6 +1347,13 @@ NK_INTERNAL void nk_e2m3_to_f32_manual_(nk_e2m3_t const *src, nk_f32_t *dest) {
|
|
|
1315
1347
|
*dest = conv.f;
|
|
1316
1348
|
}
|
|
1317
1349
|
|
|
1350
|
+
#if defined(__clang__)
|
|
1351
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
1352
|
+
#elif defined(__GNUC__)
|
|
1353
|
+
#pragma GCC push_options
|
|
1354
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
1355
|
+
#endif
|
|
1356
|
+
|
|
1318
1357
|
NK_PUBLIC void nk_e2m3_to_f32_serial(nk_e2m3_t const *src, nk_f32_t *dest) {
|
|
1319
1358
|
static nk_u32_t const lut[32] = {
|
|
1320
1359
|
0x00000000, 0x3E000000, 0x3E800000, 0x3EC00000, 0x3F000000, 0x3F200000, 0x3F400000, 0x3F600000, // exp=0 sub
|
|
@@ -1415,6 +1454,12 @@ NK_PUBLIC void nk_f32_to_e2m3_serial(nk_f32_t const *src, nk_e2m3_t *dest) {
|
|
|
1415
1454
|
*dest = (nk_e2m3_t)(sign | (exp_field << 3) | mant_field);
|
|
1416
1455
|
}
|
|
1417
1456
|
|
|
1457
|
+
#if defined(__clang__)
|
|
1458
|
+
#pragma clang attribute pop
|
|
1459
|
+
#elif defined(__GNUC__)
|
|
1460
|
+
#pragma GCC pop_options
|
|
1461
|
+
#endif
|
|
1462
|
+
|
|
1418
1463
|
/**
|
|
1419
1464
|
* @brief Convert FP6 E3M2FN to IEEE 754 single-precision float.
|
|
1420
1465
|
*
|
|
@@ -1452,6 +1497,13 @@ NK_INTERNAL void nk_e3m2_to_f32_manual_(nk_e3m2_t const *src, nk_f32_t *dest) {
|
|
|
1452
1497
|
*dest = conv.f;
|
|
1453
1498
|
}
|
|
1454
1499
|
|
|
1500
|
+
#if defined(__clang__)
|
|
1501
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
1502
|
+
#elif defined(__GNUC__)
|
|
1503
|
+
#pragma GCC push_options
|
|
1504
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
1505
|
+
#endif
|
|
1506
|
+
|
|
1455
1507
|
NK_PUBLIC void nk_e3m2_to_f32_serial(nk_e3m2_t const *src, nk_f32_t *dest) {
|
|
1456
1508
|
static nk_u32_t const lut[32] = {
|
|
1457
1509
|
0x00000000, 0x3D800000, 0x3E000000, 0x3E400000, // exp=0 sub
|
|
@@ -1557,6 +1609,12 @@ NK_PUBLIC void nk_f32_to_e3m2_serial(nk_f32_t const *src, nk_e3m2_t *dest) {
|
|
|
1557
1609
|
*dest = (nk_e3m2_t)(sign | (exp_field << 2) | mant_field);
|
|
1558
1610
|
}
|
|
1559
1611
|
|
|
1612
|
+
#if defined(__clang__)
|
|
1613
|
+
#pragma clang attribute pop
|
|
1614
|
+
#elif defined(__GNUC__)
|
|
1615
|
+
#pragma GCC pop_options
|
|
1616
|
+
#endif
|
|
1617
|
+
|
|
1560
1618
|
NK_INTERNAL void nk_f16_to_f64_serial(nk_f16_t const *x, nk_f64_t *y) {
|
|
1561
1619
|
nk_f32_t f32;
|
|
1562
1620
|
nk_f16_to_f32_serial(x, &f32);
|
|
@@ -1731,6 +1789,13 @@ NK_INTERNAL void nk_u64_to_bf16_serial(nk_u64_t const *x, nk_bf16_t *y) {
|
|
|
1731
1789
|
nk_f32_to_bf16_serial(&f32, y);
|
|
1732
1790
|
}
|
|
1733
1791
|
|
|
1792
|
+
#if defined(__clang__)
|
|
1793
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
1794
|
+
#elif defined(__GNUC__)
|
|
1795
|
+
#pragma GCC push_options
|
|
1796
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
1797
|
+
#endif
|
|
1798
|
+
|
|
1734
1799
|
/** @brief Convert a pair of i4 (4-bit signed integer, -8 to 7) nibbles into signed integers. */
|
|
1735
1800
|
NK_PUBLIC void nk_i4x2_to_i8x2_serial(nk_i4x2_t const *src, nk_i8_t *dest) {
|
|
1736
1801
|
nk_u8_t byte = *(nk_u8_t const *)src;
|
|
@@ -1776,6 +1841,12 @@ typedef union NK_MAY_ALIAS_ nk_scalar_buffer_t {
|
|
|
1776
1841
|
nk_u8_t u8;
|
|
1777
1842
|
} nk_scalar_buffer_t;
|
|
1778
1843
|
|
|
1844
|
+
#if defined(__clang__)
|
|
1845
|
+
#pragma clang attribute pop
|
|
1846
|
+
#elif defined(__GNUC__)
|
|
1847
|
+
#pragma GCC pop_options
|
|
1848
|
+
#endif
|
|
1849
|
+
|
|
1779
1850
|
/** @brief Reads a typed scalar from @p buf and writes the widened f64c into @p result.
|
|
1780
1851
|
* Real types set `.imag = 0`. Safe when @p result aliases @p buf (in-place conversion).
|
|
1781
1852
|
* @return 1 on success, 0 for unsupported types (sub-byte, unknown). */
|
|
@@ -2247,6 +2318,13 @@ NK_INTERNAL int nk_scalar_buffer_from_f64(nk_f64_t const *value, nk_scalar_buffe
|
|
|
2247
2318
|
|
|
2248
2319
|
#pragma region Public API
|
|
2249
2320
|
|
|
2321
|
+
#if defined(__clang__)
|
|
2322
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
2323
|
+
#elif defined(__GNUC__)
|
|
2324
|
+
#pragma GCC push_options
|
|
2325
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
2326
|
+
#endif
|
|
2327
|
+
|
|
2250
2328
|
NK_PUBLIC void nk_cast_serial(void const *from, nk_dtype_t from_type, nk_size_t n, void *to, nk_dtype_t to_type) {
|
|
2251
2329
|
if (from_type == to_type) {
|
|
2252
2330
|
nk_size_t size_bits = nk_dtype_bits(from_type);
|