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/numkong/swift/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Packing handles internal layout itself.
|
|
|
43
43
|
|
|
44
44
|
## Ecosystem Comparison
|
|
45
45
|
|
|
46
|
-
| Feature | NumKong | Accelerate/vDSP | [MLX]
|
|
46
|
+
| Feature | NumKong | Accelerate/vDSP | [MLX][mlx] |
|
|
47
47
|
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
48
48
|
| Operation families | dots, distances, binary, geospatial, MaxSim | dots, distances, FFT, some BLAS | matmul, elementwise, reductions, FFT |
|
|
49
49
|
| Precision | BFloat16 through sub-byte — Float8, Float6, packed bits; automatic widening; Kahan summation; 0 ULP in Float32/Float64 | Float32/Float64, limited Float16; no auto-widening; IEEE defaults | Float16/BFloat16/Float32; no Float8 or sub-byte; backend-dependent |
|
|
@@ -53,6 +53,8 @@ Packing handles internal layout itself.
|
|
|
53
53
|
| Collection-based API | works with any `RandomAccessCollection` conforming type | pointer-based vDSP functions | `MLXArray`-based |
|
|
54
54
|
| Memory model | caller-owned buffers; `Tensor`/`PackedMatrix` own their storage | caller-managed via `UnsafePointer` | graph-managed; implicit allocation and caching |
|
|
55
55
|
|
|
56
|
+
[mlx]: https://github.com/ml-explore/mlx
|
|
57
|
+
|
|
56
58
|
## Installation
|
|
57
59
|
|
|
58
60
|
Add NumKong to `Package.swift`:
|
|
@@ -74,7 +76,7 @@ Then add the product to your target:
|
|
|
74
76
|
)
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
The root package manifest already exposes the `NumKong` and `
|
|
79
|
+
The root package manifest already exposes the `NumKong` and `CNumKongDispatch` library products.
|
|
78
80
|
Xcode package integration uses the same URL.
|
|
79
81
|
|
|
80
82
|
## Collection-Based Dot Products
|
|
@@ -415,7 +417,7 @@ let haversine = haversine(aLat: libertyLat, aLon: libertyLon, bLat: bigBenLat, b
|
|
|
415
417
|
|
|
416
418
|
The low-level `UnsafeBufferPointer` static methods on `Float64` and `Float32` remain available for zero-copy use cases.
|
|
417
419
|
|
|
418
|
-
## Runtime Capabilities
|
|
420
|
+
## Runtime Capabilities
|
|
419
421
|
|
|
420
422
|
Capability detection is exposed directly for diagnostics and tests:
|
|
421
423
|
|
package/numkong/test/README.md
CHANGED
|
@@ -70,10 +70,10 @@ Foreign flag mapping for muscle-memory compatibility:
|
|
|
70
70
|
|
|
71
71
|
Each kernel is assigned a __comparison family__ that determines which error metrics are reported and what constitutes failure.
|
|
72
72
|
Families are defined in `test.hpp` as `comparison_family_t`.
|
|
73
|
-
All floating-point families report `max_abs`, `max_rel`, `mean_ulp
|
|
73
|
+
All floating-point families report `max_abs`, `max_rel`, and `mean_ulp`; most also report `max_ulp` and `exact` match counts, and some substitute `mean_abs` or `mean_rel`.
|
|
74
74
|
|
|
75
75
|
- __`exact_k`__ — integer and binary metrics: Hamming, Jaccard, set intersections, integer min/max.
|
|
76
|
-
Reports `max_dist`, `mean_dist`, `mismatch`, `exact`.
|
|
76
|
+
Reports `max_dist`, `mean_dist`, `max_abs`, `mismatch`, `exact`.
|
|
77
77
|
Fails on any `max_dist > 0`.
|
|
78
78
|
- __`narrow_arithmetic_k`__ — elementwise float ops: sum, scale, blend, fma, sin, cos, atan, cast.
|
|
79
79
|
Fails on `max_ulp > NK_ULP_THRESHOLD_{F32,F16,BF16}`.
|
|
@@ -139,7 +139,7 @@ The toolchain files configure memory limits appropriate for each target:
|
|
|
139
139
|
| ------ | -------------- | ---------- | ------------- | ------------------ |
|
|
140
140
|
| wasm32 | 64 MB | 2 GB | 32-bit | 3.1.27+ |
|
|
141
141
|
| wasm64 | 256 MB | 16 GB | 64-bit | 3.1.35+ |
|
|
142
|
-
| WASI | — |
|
|
142
|
+
| WASI | — | 2 GB | 32-bit | — |
|
|
143
143
|
|
|
144
144
|
All three targets enable `-msimd128` and `-mrelaxed-simd` automatically.
|
|
145
145
|
Stack size is 5 MB across all Emscripten targets.
|
|
@@ -156,7 +156,7 @@ For up-to-date engine support, see [WebAssembly Roadmap](https://webassembly.org
|
|
|
156
156
|
|
|
157
157
|
### Cross-Compilation
|
|
158
158
|
|
|
159
|
-
NumKong ships
|
|
159
|
+
NumKong ships 11 toolchain files in `cmake/` for cross-compiling to non-native targets.
|
|
160
160
|
Tests run transparently under QEMU via `CMAKE_CROSSCOMPILING_EMULATOR`.
|
|
161
161
|
Set `NK_IN_QEMU=1` to relax half-precision accuracy thresholds under emulation.
|
|
162
162
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Minimal WASI command runner: `node --experimental-wasi-unstable-preview1 run-wasi.mjs <module.wasm> [args...]`
|
|
2
|
+
// Lets CTest use node as a cross-runtime engine for the portable WASI tests, alongside wasmtime and wasmer
|
|
3
|
+
// (see cmake/toolchain-wasi.cmake, NK_WASM_RUNTIME=node). Node cannot execute a bare `.wasm` from the CLI,
|
|
4
|
+
// so this thin wrapper instantiates it with a WASI import object and forwards the process exit code.
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { WASI } from 'node:wasi';
|
|
7
|
+
import { argv, exit } from 'node:process';
|
|
8
|
+
|
|
9
|
+
const modulePath = argv[2];
|
|
10
|
+
if (!modulePath) {
|
|
11
|
+
console.error('usage: node --experimental-wasi-unstable-preview1 run-wasi.mjs <module.wasm> [args...]');
|
|
12
|
+
exit(2);
|
|
13
|
+
}
|
|
14
|
+
const wasi = new WASI({ version: 'preview1', args: argv.slice(2), returnOnExit: true });
|
|
15
|
+
const module = new WebAssembly.Module(readFileSync(modulePath));
|
|
16
|
+
const instance = new WebAssembly.Instance(module, wasi.getImportObject());
|
|
17
|
+
exit(wasi.start(instance));
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
#include "test.hpp"
|
|
9
|
-
#include "numkong/each.hpp"
|
|
9
|
+
#include "numkong/each.hpp" // `nk::sum`, `nk::scale`, `nk::blend`, `nk::fma`
|
|
10
|
+
#include "numkong/trigonometry.hpp" // `nk::try_sin`, `nk::try_cos`, `nk::try_atan` wrappers
|
|
10
11
|
|
|
11
12
|
template <typename scalar_type_, typename generator_type_>
|
|
12
13
|
typename scalar_type_::scale_t random_coef(generator_type_ &gen) {
|
|
@@ -150,9 +151,37 @@ error_stats_t test_fma(typename scalar_type_::fma_kernel_t kernel) {
|
|
|
150
151
|
return stats;
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
/**
|
|
155
|
+
* @brief Smoke-test for the tensor-shaped trig wrappers (`nk::try_sin`/`cos`/`atan`).
|
|
156
|
+
* Runs allocating + into-span variants on a small zero tensor — just exercises the dispatch
|
|
157
|
+
* paths, not the numerical accuracy (the latter is covered by the kernel tests above).
|
|
158
|
+
*/
|
|
159
|
+
template <typename value_type_>
|
|
160
|
+
void test_tensor_trig_for_type() {
|
|
161
|
+
using tensor_t = nk::tensor<value_type_>;
|
|
162
|
+
auto a = tensor_t::try_zeros({4, 8});
|
|
163
|
+
auto out = tensor_t::try_zeros({4, 8});
|
|
164
|
+
auto av = a.view();
|
|
165
|
+
|
|
166
|
+
{ [[maybe_unused]] auto r = nk::try_sin<value_type_>(av); }
|
|
167
|
+
{ [[maybe_unused]] auto r = nk::try_cos<value_type_>(av); }
|
|
168
|
+
{ [[maybe_unused]] auto r = nk::try_atan<value_type_>(av); }
|
|
169
|
+
{ [[maybe_unused]] bool ok = nk::sin<value_type_>(av, out.span()); }
|
|
170
|
+
{ [[maybe_unused]] bool ok = nk::cos<value_type_>(av, out.span()); }
|
|
171
|
+
{ [[maybe_unused]] bool ok = nk::atan<value_type_>(av, out.span()); }
|
|
172
|
+
}
|
|
173
|
+
|
|
153
174
|
void test_each() {
|
|
154
175
|
error_stats_section_t check("Elementwise Operations");
|
|
155
176
|
|
|
177
|
+
// Tensor-shaped trig wrappers (float-capable types).
|
|
178
|
+
test_tensor_trig_for_type<nk::f32_t>();
|
|
179
|
+
test_tensor_trig_for_type<nk::f64_t>();
|
|
180
|
+
test_tensor_trig_for_type<nk::f16_t>();
|
|
181
|
+
test_tensor_trig_for_type<nk::bf16_t>();
|
|
182
|
+
std::printf(" trig (4 types): OK\n");
|
|
183
|
+
|
|
184
|
+
|
|
156
185
|
#if NK_DYNAMIC_DISPATCH
|
|
157
186
|
// Dynamic dispatch - only test the dispatcher itself
|
|
158
187
|
check("each_scale_f32", test_scale<f32_t>, nk_each_scale_f32);
|
|
@@ -405,7 +405,7 @@ def test_add_multiply_noncontiguous(dtype: str, kernel, capability: str):
|
|
|
405
405
|
if mismatch_count:
|
|
406
406
|
collect_warnings(f"NumPy overflow in ({a.dtype} {operator} {b.dtype} -> {output_dtype})", stats)
|
|
407
407
|
|
|
408
|
-
# out= with nk.Tensor buffer
|
|
408
|
+
# out= with nk.Tensor buffer; differing dtypes are covered by test_strided_out_with_dtype_change
|
|
409
409
|
if first_dtype == second_dtype == output_dtype and inplace_numkong.ndim == 1:
|
|
410
410
|
out_nk = nk.zeros(inplace_numkong.shape, dtype=output_dtype)
|
|
411
411
|
ret = simd_kernel(a, b, out=out_nk)
|
|
@@ -754,3 +754,76 @@ def test_blend_known(ndim: int, dtype: str, capability: str):
|
|
|
754
754
|
result = list(nk.blend(a, b, alpha=2.0, beta=3.0))
|
|
755
755
|
for i in range(ndim):
|
|
756
756
|
assert abs(result[i] - expected) < NK_ATOL, f"blend[{i}] = {result[i]}, expected {expected}"
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
760
|
+
@pytest.mark.parametrize("kernel", ["add", "multiply"])
|
|
761
|
+
@pytest.mark.parametrize("second_is_scalar", [True, False], ids=["scalar_b", "array_b"])
|
|
762
|
+
@pytest.mark.parametrize("capability", possible_capabilities)
|
|
763
|
+
def test_strided_out_with_dtype_change(kernel: str, second_is_scalar: bool, capability: str):
|
|
764
|
+
"""A strided `out` of a differing dtype must be scattered, not packed over its neighbours.
|
|
765
|
+
|
|
766
|
+
The scalar and array forms of `b` select different implementations, and each of
|
|
767
|
+
`add`/`multiply` has its own pair, so this covers all four writeback paths.
|
|
768
|
+
"""
|
|
769
|
+
keep_one_capability(capability)
|
|
770
|
+
simd_kernel = getattr(nk, kernel)
|
|
771
|
+
first = np.arange(6, dtype=np.int16)
|
|
772
|
+
second = 5 if second_is_scalar else np.arange(6, dtype=np.int16)
|
|
773
|
+
expected = first + second if kernel == "add" else first * second
|
|
774
|
+
|
|
775
|
+
# Column 0 of a 2-column array: same shape as the input, stride of two float64s.
|
|
776
|
+
surrounding = np.zeros((6, 2), dtype=np.float64)
|
|
777
|
+
simd_kernel(first, second, out=surrounding[:, 0])
|
|
778
|
+
assert_allclose(surrounding[:, 0], expected.astype(np.float64), atol=NK_ATOL, rtol=NK_RTOL)
|
|
779
|
+
assert not surrounding[:, 1].any(), "writeback spilled into the neighbouring column"
|
|
780
|
+
|
|
781
|
+
# Reversed view exercises the negative-stride path.
|
|
782
|
+
reversed_out = np.zeros(6, dtype=np.float64)[::-1]
|
|
783
|
+
simd_kernel(first, second, out=reversed_out)
|
|
784
|
+
assert_allclose(reversed_out, expected.astype(np.float64), atol=NK_ATOL, rtol=NK_RTOL)
|
|
785
|
+
|
|
786
|
+
# A packed `out` must still take the single bulk-cast path.
|
|
787
|
+
contiguous_out = np.zeros(6, dtype=np.float64)
|
|
788
|
+
simd_kernel(first, second, out=contiguous_out)
|
|
789
|
+
assert_allclose(contiguous_out, expected.astype(np.float64), atol=NK_ATOL, rtol=NK_RTOL)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
793
|
+
@pytest.mark.parametrize("kernel", ["add", "multiply"])
|
|
794
|
+
def test_strided_out_multidimensional(kernel: str):
|
|
795
|
+
"""Rank-2 and rank-3 outputs cover both a fully scattered tail and a partially packed one."""
|
|
796
|
+
simd_kernel = getattr(nk, kernel)
|
|
797
|
+
|
|
798
|
+
# Rank 2, innermost dimension strided: no contiguous tail at all.
|
|
799
|
+
first = np.arange(12, dtype=np.int16).reshape(3, 4)
|
|
800
|
+
expected = first + 2 if kernel == "add" else first * 2
|
|
801
|
+
surrounding = np.zeros((3, 4, 2), dtype=np.float64)
|
|
802
|
+
simd_kernel(first, 2, out=surrounding[:, :, 0])
|
|
803
|
+
assert_allclose(surrounding[:, :, 0], expected.astype(np.float64), atol=NK_ATOL, rtol=NK_RTOL)
|
|
804
|
+
assert not surrounding[:, :, 1].any(), "writeback spilled into the neighbouring column"
|
|
805
|
+
|
|
806
|
+
# Rank 3 with a packed innermost pair: recursion plus a bulk slice per row.
|
|
807
|
+
first = np.arange(24, dtype=np.int16).reshape(2, 6, 2)
|
|
808
|
+
expected = first + 3 if kernel == "add" else first * 3
|
|
809
|
+
surrounding = np.zeros((2, 6, 4), dtype=np.float64)
|
|
810
|
+
simd_kernel(first, 3, out=surrounding[:, :, 0:2])
|
|
811
|
+
assert_allclose(surrounding[:, :, 0:2], expected.astype(np.float64), atol=NK_ATOL, rtol=NK_RTOL)
|
|
812
|
+
assert not surrounding[:, :, 2:].any(), "writeback spilled past the requested columns"
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
816
|
+
@pytest.mark.parametrize(
|
|
817
|
+
"dtype,distinct_values",
|
|
818
|
+
[pytest.param("uint4", 8, id="uint4"), pytest.param("int4", 8, id="int4"), pytest.param("uint1", 2, id="uint1")],
|
|
819
|
+
)
|
|
820
|
+
def test_packed_out_requires_contiguity(dtype: str, distinct_values: int):
|
|
821
|
+
"""Sub-byte dtypes have no per-value byte stride, so only a packed `out` can be filled."""
|
|
822
|
+
first = np.arange(8, dtype=np.uint8) % distinct_values
|
|
823
|
+
|
|
824
|
+
packed_out = nk.zeros(first.shape, dtype=dtype)
|
|
825
|
+
nk.add(first, 0, out=packed_out)
|
|
826
|
+
assert_allclose(np.asarray(nk.astype(packed_out, "uint8")), first, atol=NK_ATOL, rtol=NK_RTOL)
|
|
827
|
+
|
|
828
|
+
with pytest.raises(ValueError, match="C-contiguous"):
|
|
829
|
+
nk.add(first[:4], 0, out=nk.zeros(first.shape, dtype=dtype)[::2])
|
|
@@ -62,9 +62,60 @@ error_stats_t test_reduce_minmax(typename input_type_::reduce_minmax_kernel_t ke
|
|
|
62
62
|
return stats;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @brief Smoke-test for the vector-shaped reduction wrappers (`nk::moments`/`minmax`/`sum`/...).
|
|
67
|
+
* Exercises every accessor variant on a small random vector. Numerical accuracy of the
|
|
68
|
+
* underlying kernels is validated by `test_reduce_moments` / `test_reduce_minmax` above.
|
|
69
|
+
*/
|
|
70
|
+
template <typename value_type_>
|
|
71
|
+
void test_vector_reductions_for_type() {
|
|
72
|
+
auto v = make_vector<value_type_>(32);
|
|
73
|
+
std::mt19937 generator(42);
|
|
74
|
+
fill_random(generator, v);
|
|
75
|
+
auto view = nk::vector_view<value_type_>(v.values_data(), static_cast<std::size_t>(v.size()));
|
|
76
|
+
|
|
77
|
+
{ [[maybe_unused]] auto r = nk::moments(view); }
|
|
78
|
+
{ [[maybe_unused]] auto r = nk::minmax(view); }
|
|
79
|
+
{ [[maybe_unused]] auto r = nk::sum(view); }
|
|
80
|
+
{ [[maybe_unused]] auto r = nk::min(view); }
|
|
81
|
+
{ [[maybe_unused]] auto r = nk::max(view); }
|
|
82
|
+
{ [[maybe_unused]] auto r = nk::argmin(view); }
|
|
83
|
+
{ [[maybe_unused]] auto r = nk::argmax(view); }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @brief Golden-value test for the vector-shaped reduction wrappers.
|
|
88
|
+
*/
|
|
89
|
+
inline void test_vector_reductions_correctness() {
|
|
90
|
+
nk::f32_t data[] = {nk::f32_t(1), nk::f32_t(2), nk::f32_t(3), nk::f32_t(4), nk::f32_t(5)};
|
|
91
|
+
auto view = nk::vector_view<nk::f32_t>(data, std::size_t {5});
|
|
92
|
+
|
|
93
|
+
auto m = nk::moments(view);
|
|
94
|
+
assert(m.sum.raw_ == 15.0 && "vector moments sum");
|
|
95
|
+
assert(m.sumsq.raw_ == 55.0 && "vector moments sumsq");
|
|
96
|
+
|
|
97
|
+
auto mm = nk::minmax(view);
|
|
98
|
+
assert(mm.min_value.raw_ == 1.0f && "vector min");
|
|
99
|
+
assert(mm.max_value.raw_ == 5.0f && "vector max");
|
|
100
|
+
assert(mm.min_index == 0 && "vector argmin");
|
|
101
|
+
assert(mm.max_index == 4 && "vector argmax");
|
|
102
|
+
}
|
|
103
|
+
|
|
65
104
|
void test_reduce() {
|
|
66
105
|
error_stats_section_t check("Reductions");
|
|
67
106
|
|
|
107
|
+
// Tensor- and vector-shaped wrapper smoke tests (independent of ISA dispatch).
|
|
108
|
+
test_vector_reductions_for_type<nk::f32_t>();
|
|
109
|
+
test_vector_reductions_for_type<nk::f64_t>();
|
|
110
|
+
test_vector_reductions_for_type<nk::f16_t>();
|
|
111
|
+
test_vector_reductions_for_type<nk::bf16_t>();
|
|
112
|
+
test_vector_reductions_for_type<nk::i8_t>();
|
|
113
|
+
test_vector_reductions_for_type<nk::u8_t>();
|
|
114
|
+
std::printf(" vector reductions (6 types): OK\n");
|
|
115
|
+
|
|
116
|
+
test_vector_reductions_correctness();
|
|
117
|
+
std::printf(" vector reductions correct: OK\n");
|
|
118
|
+
|
|
68
119
|
#if NK_DYNAMIC_DISPATCH
|
|
69
120
|
check("reduce_moments_f32", test_reduce_moments<f32_t>, nk_reduce_moments_f32, nk_reduce_moments_f32_serial);
|
|
70
121
|
check("reduce_moments_f64", test_reduce_moments<f64_t>, nk_reduce_moments_f64, nk_reduce_moments_f64_serial);
|
|
@@ -679,21 +679,6 @@ void test_tensor_ops_for_type() {
|
|
|
679
679
|
}
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
-
template <typename value_type_>
|
|
683
|
-
void test_tensor_trig_for_type() {
|
|
684
|
-
using tensor_t = nk::tensor<value_type_>;
|
|
685
|
-
auto a = tensor_t::try_zeros({4, 8});
|
|
686
|
-
auto out = tensor_t::try_zeros({4, 8});
|
|
687
|
-
auto av = a.view();
|
|
688
|
-
|
|
689
|
-
{ [[maybe_unused]] auto r = nk::try_sin<value_type_>(av); }
|
|
690
|
-
{ [[maybe_unused]] auto r = nk::try_cos<value_type_>(av); }
|
|
691
|
-
{ [[maybe_unused]] auto r = nk::try_atan<value_type_>(av); }
|
|
692
|
-
{ [[maybe_unused]] bool ok = nk::sin<value_type_>(av, out.span()); }
|
|
693
|
-
{ [[maybe_unused]] bool ok = nk::cos<value_type_>(av, out.span()); }
|
|
694
|
-
{ [[maybe_unused]] bool ok = nk::atan<value_type_>(av, out.span()); }
|
|
695
|
-
}
|
|
696
|
-
|
|
697
682
|
template <typename value_type_>
|
|
698
683
|
void test_tensor_symmetric_for_type() {
|
|
699
684
|
using tensor_t = nk::tensor<value_type_>;
|
|
@@ -761,37 +746,6 @@ void test_custom_allocator_try_fns() {
|
|
|
761
746
|
{ auto r = nk::try_sum<nk::f32_t, 8, sum_alloc_t>(av, 0); }
|
|
762
747
|
}
|
|
763
748
|
|
|
764
|
-
template <typename value_type_>
|
|
765
|
-
void test_vector_reductions_for_type() {
|
|
766
|
-
auto v = make_vector<value_type_>(32);
|
|
767
|
-
std::mt19937 generator(42);
|
|
768
|
-
fill_random(generator, v);
|
|
769
|
-
auto view = nk::vector_view<value_type_>(v.values_data(), static_cast<std::size_t>(v.size()));
|
|
770
|
-
|
|
771
|
-
{ [[maybe_unused]] auto r = nk::moments(view); }
|
|
772
|
-
{ [[maybe_unused]] auto r = nk::minmax(view); }
|
|
773
|
-
{ [[maybe_unused]] auto r = nk::sum(view); }
|
|
774
|
-
{ [[maybe_unused]] auto r = nk::min(view); }
|
|
775
|
-
{ [[maybe_unused]] auto r = nk::max(view); }
|
|
776
|
-
{ [[maybe_unused]] auto r = nk::argmin(view); }
|
|
777
|
-
{ [[maybe_unused]] auto r = nk::argmax(view); }
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
void test_vector_reductions_correctness() {
|
|
781
|
-
nk::f32_t data[] = {nk::f32_t(1), nk::f32_t(2), nk::f32_t(3), nk::f32_t(4), nk::f32_t(5)};
|
|
782
|
-
auto view = nk::vector_view<nk::f32_t>(data, std::size_t {5});
|
|
783
|
-
|
|
784
|
-
auto m = nk::moments(view);
|
|
785
|
-
assert(m.sum.raw_ == 15.0 && "vector moments sum");
|
|
786
|
-
assert(m.sumsq.raw_ == 55.0 && "vector moments sumsq");
|
|
787
|
-
|
|
788
|
-
auto mm = nk::minmax(view);
|
|
789
|
-
assert(mm.min_value.raw_ == 1.0f && "vector min");
|
|
790
|
-
assert(mm.max_value.raw_ == 5.0f && "vector max");
|
|
791
|
-
assert(mm.min_index == 0 && "vector argmin");
|
|
792
|
-
assert(mm.max_index == 4 && "vector argmax");
|
|
793
|
-
}
|
|
794
|
-
|
|
795
749
|
template <typename from_type_, typename to_type_>
|
|
796
750
|
void test_cast_for_types() {
|
|
797
751
|
auto src = make_vector<from_type_>(64);
|
|
@@ -884,12 +838,7 @@ void test_tensor_ops() {
|
|
|
884
838
|
test_packed_tensor_fail_closed_views();
|
|
885
839
|
std::printf(" tensor fail-closed views: OK\n");
|
|
886
840
|
|
|
887
|
-
// Trig
|
|
888
|
-
test_tensor_trig_for_type<nk::f32_t>();
|
|
889
|
-
test_tensor_trig_for_type<nk::f64_t>();
|
|
890
|
-
test_tensor_trig_for_type<nk::f16_t>();
|
|
891
|
-
test_tensor_trig_for_type<nk::bf16_t>();
|
|
892
|
-
std::printf(" trig (4 types): OK\n");
|
|
841
|
+
// (Trig wrappers moved to test_each.cpp — they are elementwise operations.)
|
|
893
842
|
|
|
894
843
|
// Symmetric distances
|
|
895
844
|
test_tensor_symmetric_for_type<nk::f32_t>();
|
|
@@ -919,17 +868,7 @@ void test_tensor_ops() {
|
|
|
919
868
|
test_custom_allocator_try_fns();
|
|
920
869
|
std::printf(" custom allocator try_fns: OK\n");
|
|
921
870
|
|
|
922
|
-
// Vector-level
|
|
923
|
-
test_vector_reductions_for_type<nk::f32_t>();
|
|
924
|
-
test_vector_reductions_for_type<nk::f64_t>();
|
|
925
|
-
test_vector_reductions_for_type<nk::f16_t>();
|
|
926
|
-
test_vector_reductions_for_type<nk::bf16_t>();
|
|
927
|
-
test_vector_reductions_for_type<nk::i8_t>();
|
|
928
|
-
test_vector_reductions_for_type<nk::u8_t>();
|
|
929
|
-
std::printf(" vector reductions (6 types): OK\n");
|
|
930
|
-
|
|
931
|
-
test_vector_reductions_correctness();
|
|
932
|
-
std::printf(" vector reductions correct: OK\n");
|
|
871
|
+
// (Vector-level reduction wrappers moved to test_reduce.cpp.)
|
|
933
872
|
|
|
934
873
|
// Cast wrapper
|
|
935
874
|
test_cast_for_types<nk::f32_t, nk::f16_t>();
|
|
@@ -102,6 +102,99 @@ KERNELS_TENSOR: dict[str, tuple[Callable, Callable, Callable | None]] = {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
106
|
+
def test_astype_accepts_ndarray_without_tensor_copy():
|
|
107
|
+
"""Top-level astype converts an N-D NumPy buffer while preserving its shape."""
|
|
108
|
+
source = np.arange(24, dtype=np.uint8).reshape(2, 3, 4)
|
|
109
|
+
|
|
110
|
+
result = nk.astype(source, "float32")
|
|
111
|
+
|
|
112
|
+
assert result.shape == source.shape
|
|
113
|
+
assert result.dtype == "float32"
|
|
114
|
+
assert_allclose(np.asarray(result), source.astype(np.float32))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
118
|
+
def test_astype_writes_to_caller_owned_output():
|
|
119
|
+
"""Top-level astype converts directly into a matching caller-owned buffer and returns it."""
|
|
120
|
+
source = np.arange(12, dtype=np.float32).reshape(3, 4) + 0.75
|
|
121
|
+
output = np.empty(source.shape, dtype=np.uint8)
|
|
122
|
+
|
|
123
|
+
assert nk.astype(source, "uint8", out=output) is output
|
|
124
|
+
assert_allclose(output, source.astype(np.uint8))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
128
|
+
@pytest.mark.parametrize("dtype", ["bfloat16", "uint4"])
|
|
129
|
+
def test_astype_writes_to_tensor_output(dtype):
|
|
130
|
+
"""A NumKong Tensor names dtypes NumPy cannot express, so it must be accepted as `out`."""
|
|
131
|
+
source = np.arange(8, dtype=np.uint8)
|
|
132
|
+
output = nk.empty(source.shape, dtype=dtype)
|
|
133
|
+
|
|
134
|
+
assert nk.astype(source, dtype, out=output) is output
|
|
135
|
+
np.testing.assert_array_equal(np.asarray(nk.astype(output, "uint8")), source)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
139
|
+
def test_astype_linearizes_strided_input():
|
|
140
|
+
"""Top-level astype accepts a non-contiguous input buffer."""
|
|
141
|
+
source = np.arange(48, dtype=np.int16).reshape(2, 3, 8)[:, ::-1, ::2]
|
|
142
|
+
|
|
143
|
+
result = nk.astype(source, "float32")
|
|
144
|
+
|
|
145
|
+
assert result.shape == source.shape
|
|
146
|
+
assert_allclose(np.asarray(result), source.astype(np.float32))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
150
|
+
def test_astype_float_to_integer_policy():
|
|
151
|
+
"""Float-to-integer casts round ties to even, saturate, and map NaN to zero."""
|
|
152
|
+
source = np.array([-np.inf, -127.5, -0.5, 0.5, 1.5, 126.5, np.inf, np.nan], dtype=np.float64)
|
|
153
|
+
|
|
154
|
+
result = nk.astype(source, "int8")
|
|
155
|
+
|
|
156
|
+
np.testing.assert_array_equal(np.asarray(result), np.array([-128, -128, 0, 0, 2, 126, 127, 0], dtype=np.int8))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
160
|
+
def test_astype_round_trips_through_packed_dtype():
|
|
161
|
+
"""Top-level astype preserves logical values when a packed Tensor is used in between."""
|
|
162
|
+
source = np.arange(8, dtype=np.uint8)
|
|
163
|
+
|
|
164
|
+
packed = nk.astype(source, "uint4")
|
|
165
|
+
result = nk.astype(packed, "uint8")
|
|
166
|
+
|
|
167
|
+
assert packed.dtype == "uint4"
|
|
168
|
+
np.testing.assert_array_equal(np.asarray(result), source)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
172
|
+
def test_astype_validates_output_shape_dtype_and_layout():
|
|
173
|
+
"""Top-level astype requires an exact, C-contiguous output buffer."""
|
|
174
|
+
source = np.arange(24, dtype=np.uint8).reshape(2, 3, 4)
|
|
175
|
+
|
|
176
|
+
with pytest.raises(ValueError, match="shapes don't match"):
|
|
177
|
+
nk.astype(source, "float32", out=np.empty((2, 3, 3), dtype=np.float32))
|
|
178
|
+
with pytest.raises(TypeError, match="expected"):
|
|
179
|
+
nk.astype(source, "float32", out=np.empty(source.shape, dtype=np.float64))
|
|
180
|
+
with pytest.raises(ValueError, match="C-contiguous"):
|
|
181
|
+
nk.astype(source, "float32", out=np.empty((4, 3, 2), dtype=np.float32).transpose(2, 1, 0))
|
|
182
|
+
|
|
183
|
+
readonly = np.empty(source.shape, dtype=np.float32)
|
|
184
|
+
readonly.flags.writeable = False
|
|
185
|
+
with pytest.raises((ValueError, BufferError, TypeError)):
|
|
186
|
+
nk.astype(source, "float32", out=readonly)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
190
|
+
def test_astype_rejects_overlapping_output():
|
|
191
|
+
"""Top-level astype rejects an output buffer that aliases its input."""
|
|
192
|
+
source = np.arange(12, dtype=np.float32)
|
|
193
|
+
|
|
194
|
+
with pytest.raises(ValueError, match="must not overlap"):
|
|
195
|
+
nk.astype(source, "float32", out=source)
|
|
196
|
+
|
|
197
|
+
|
|
105
198
|
def test_pointers_availability():
|
|
106
199
|
"""Tests the availability of pre-compiled functions for compatibility with USearch."""
|
|
107
200
|
assert nk.pointer_to_sqeuclidean("float64") != 0
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|