usearch 2.26.0 → 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 +2 -2
- package/cpp/test.cpp +66 -3
- package/include/usearch/index.hpp +9 -1
- package/include/usearch/index_dense.hpp +16 -5
- 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
|
@@ -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
|
|
@@ -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
|