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
|
@@ -21,7 +21,7 @@ extern "C" {
|
|
|
21
21
|
/**
|
|
22
22
|
* @brief Computes an approximate sine of the given angle in radians with @b 3-ULP error bound for [-2π, 2π].
|
|
23
23
|
* @see Based on @b `xfastsinf_u3500` in SLEEF library.
|
|
24
|
-
* @param[in]
|
|
24
|
+
* @param[in] angle_radians The input angle in radians.
|
|
25
25
|
* @return The approximate sine of the input angle in [-1, 1] range.
|
|
26
26
|
*/
|
|
27
27
|
NK_PUBLIC nk_f32_t nk_f32_sin(nk_f32_t const angle_radians) {
|
|
@@ -62,7 +62,7 @@ NK_PUBLIC nk_f32_t nk_f32_sin(nk_f32_t const angle_radians) {
|
|
|
62
62
|
/**
|
|
63
63
|
* @brief Computes an approximate cosine of the given angle in radians with @b 3-ULP error bound for [-2π, 2π].
|
|
64
64
|
* @see Based on @b `xfastcosf_u3500` in SLEEF library.
|
|
65
|
-
* @param[in]
|
|
65
|
+
* @param[in] angle_radians The input angle in radians.
|
|
66
66
|
* @return The approximate cosine of the input angle in [-1, 1] range.
|
|
67
67
|
*/
|
|
68
68
|
NK_PUBLIC nk_f32_t nk_f32_cos(nk_f32_t const angle_radians) {
|
|
@@ -255,8 +255,8 @@ NK_PUBLIC nk_f32_t nk_f32_atan2(nk_f32_t const y_input, nk_f32_t const x_input)
|
|
|
255
255
|
/**
|
|
256
256
|
* @brief Computes the sine of the given angle in radians with @b 0-ULP error bound in [-2π, 2π].
|
|
257
257
|
* @see Based on @b `xsin` in SLEEF library.
|
|
258
|
-
* @param[in]
|
|
259
|
-
* @return The approximate
|
|
258
|
+
* @param[in] angle_radians The input angle in radians.
|
|
259
|
+
* @return The approximate sine of the input angle.
|
|
260
260
|
*/
|
|
261
261
|
NK_PUBLIC nk_f64_t nk_f64_sin(nk_f64_t const angle_radians) {
|
|
262
262
|
|
|
@@ -316,7 +316,7 @@ NK_PUBLIC nk_f64_t nk_f64_sin(nk_f64_t const angle_radians) {
|
|
|
316
316
|
/**
|
|
317
317
|
* @brief Computes the cosine of the given angle in radians with @b 0-ULP error bound in [-2π, 2π].
|
|
318
318
|
* @see Based on @b `xcos` in SLEEF library.
|
|
319
|
-
* @param[in]
|
|
319
|
+
* @param[in] angle_radians The input angle in radians.
|
|
320
320
|
* @return The approximate cosine of the input angle in [-1, 1] range.
|
|
321
321
|
*/
|
|
322
322
|
NK_PUBLIC nk_f64_t nk_f64_cos(nk_f64_t const angle_radians) {
|
|
@@ -439,7 +439,7 @@ NK_PUBLIC nk_f64_t nk_f64_atan(nk_f64_t const input) {
|
|
|
439
439
|
* @see Based on @b `xatan2` in SLEEF library.
|
|
440
440
|
* @param y_input The input sine value.
|
|
441
441
|
* @param x_input The input cosine value.
|
|
442
|
-
* @return The arc-tangent of (y_input/x_input) in [
|
|
442
|
+
* @return The arc-tangent of (y_input/x_input) in [-π, π] radians range.
|
|
443
443
|
*/
|
|
444
444
|
NK_PUBLIC nk_f64_t nk_f64_atan2(nk_f64_t const y_input, nk_f64_t const x_input) {
|
|
445
445
|
// Polynomial coefficients for atan2 approximation
|
|
@@ -481,9 +481,11 @@ struct f32_t {
|
|
|
481
481
|
inline f32_t clamp(f32_t lo, f32_t hi) const noexcept { return max(lo).min(hi); }
|
|
482
482
|
|
|
483
483
|
static constexpr f32_t clamped_to_finite_(float x) noexcept {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
484
|
+
f32_t const value {x};
|
|
485
|
+
// Classify by bit pattern: arithmetic comparisons against infinity are folded away
|
|
486
|
+
// under `-ffinite-math-only`, silently disabling the clamp.
|
|
487
|
+
if (value.is_infinite()) return value.is_sign_negative() ? finite_min() : finite_max();
|
|
488
|
+
return value;
|
|
487
489
|
}
|
|
488
490
|
|
|
489
491
|
/** @brief Saturating addition: clamps to finite range on overflow. */
|
|
@@ -751,9 +753,11 @@ struct f64_t {
|
|
|
751
753
|
inline f64_t clamp(f64_t lo, f64_t hi) const noexcept { return max(lo).min(hi); }
|
|
752
754
|
|
|
753
755
|
static constexpr f64_t clamped_to_finite_(double x) noexcept {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
756
|
+
f64_t const value {x};
|
|
757
|
+
// Classify by bit pattern: arithmetic comparisons against infinity are folded away
|
|
758
|
+
// under `-ffinite-math-only`, silently disabling the clamp.
|
|
759
|
+
if (value.is_infinite()) return value.is_sign_negative() ? finite_min() : finite_max();
|
|
760
|
+
return value;
|
|
757
761
|
}
|
|
758
762
|
|
|
759
763
|
/** @brief Saturating addition: clamps to finite range on overflow. */
|
|
@@ -3062,11 +3066,16 @@ struct f118_t {
|
|
|
3062
3066
|
|
|
3063
3067
|
/** @brief Returns true if the value is positive or negative infinity. */
|
|
3064
3068
|
constexpr bool is_infinite() const noexcept {
|
|
3065
|
-
|
|
3069
|
+
// Classify by bit pattern: arithmetic comparisons against infinity are folded away
|
|
3070
|
+
// under `-ffinite-math-only`.
|
|
3071
|
+
return (std::bit_cast<std::uint64_t>(high_) & 0x7FFFFFFFFFFFFFFFull) == 0x7FF0000000000000ull;
|
|
3066
3072
|
}
|
|
3067
3073
|
|
|
3068
3074
|
/** @brief Returns true if the value is neither infinite nor NaN. */
|
|
3069
|
-
|
|
3075
|
+
constexpr bool is_finite() const noexcept {
|
|
3076
|
+
// Classify by bit pattern: `std::isfinite` constant-folds to true under `-ffinite-math-only`.
|
|
3077
|
+
return (std::bit_cast<std::uint64_t>(high_) & 0x7FF0000000000000ull) != 0x7FF0000000000000ull;
|
|
3078
|
+
}
|
|
3070
3079
|
|
|
3071
3080
|
/** @brief Returns true if the sign bit is positive (includes +0). */
|
|
3072
3081
|
inline bool is_sign_positive() const noexcept { return high_ > 0.0 || (high_ == 0.0 && !std::signbit(high_)); }
|
|
@@ -4917,11 +4926,13 @@ struct sub_byte_ref<u1x8_t> {
|
|
|
4917
4926
|
constexpr bool get() const noexcept { return (*raw_ptr_ & bit_mask_) != 0; }
|
|
4918
4927
|
constexpr operator bool() const noexcept { return get(); }
|
|
4919
4928
|
|
|
4920
|
-
|
|
4921
|
-
|
|
4929
|
+
template <std::integral integral_type_>
|
|
4930
|
+
constexpr sub_byte_ref &operator=(integral_type_ value) noexcept {
|
|
4931
|
+
if (value != integral_type_ {0}) *raw_ptr_ |= bit_mask_;
|
|
4922
4932
|
else *raw_ptr_ &= static_cast<nk_u1x8_t>(~bit_mask_);
|
|
4923
4933
|
return *this;
|
|
4924
4934
|
}
|
|
4935
|
+
constexpr sub_byte_ref &operator=(u8_t value) noexcept { return *this = value.raw(); }
|
|
4925
4936
|
|
|
4926
4937
|
constexpr void flip() noexcept { *raw_ptr_ ^= bit_mask_; }
|
|
4927
4938
|
};
|
|
@@ -4941,11 +4952,21 @@ struct sub_byte_ref<i4x2_t> {
|
|
|
4941
4952
|
}
|
|
4942
4953
|
constexpr operator std::int8_t() const noexcept { return get(); }
|
|
4943
4954
|
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4955
|
+
template <std::integral integral_type_>
|
|
4956
|
+
constexpr sub_byte_ref &operator=(integral_type_ value) noexcept {
|
|
4957
|
+
std::int8_t clamped;
|
|
4958
|
+
if constexpr (std::is_signed_v<integral_type_>) {
|
|
4959
|
+
clamped = value < integral_type_(-8) ? std::int8_t(-8)
|
|
4960
|
+
: value > integral_type_(7) ? std::int8_t(7)
|
|
4961
|
+
: static_cast<std::int8_t>(value);
|
|
4962
|
+
}
|
|
4963
|
+
else { clamped = value > integral_type_(7) ? std::int8_t(7) : static_cast<std::int8_t>(value); }
|
|
4964
|
+
auto nibble = static_cast<nk_i4x2_t>(clamped & 0x0F);
|
|
4965
|
+
if (high_nibble_) *raw_ptr_ = static_cast<nk_i4x2_t>((*raw_ptr_ & 0x0F) | (nibble << 4));
|
|
4966
|
+
else *raw_ptr_ = static_cast<nk_i4x2_t>((*raw_ptr_ & 0xF0) | nibble);
|
|
4947
4967
|
return *this;
|
|
4948
4968
|
}
|
|
4969
|
+
constexpr sub_byte_ref &operator=(i8_t value) noexcept { return *this = value.raw(); }
|
|
4949
4970
|
};
|
|
4950
4971
|
|
|
4951
4972
|
/** @brief Unsigned 4-bit access for u4x2_t vectors (2 nibbles packed per byte). */
|
|
@@ -4960,11 +4981,20 @@ struct sub_byte_ref<u4x2_t> {
|
|
|
4960
4981
|
constexpr std::uint8_t get() const noexcept { return high_nibble_ ? (*raw_ptr_ >> 4) : (*raw_ptr_ & 0x0F); }
|
|
4961
4982
|
constexpr operator std::uint8_t() const noexcept { return get(); }
|
|
4962
4983
|
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4984
|
+
template <std::integral integral_type_>
|
|
4985
|
+
constexpr sub_byte_ref &operator=(integral_type_ value) noexcept {
|
|
4986
|
+
std::uint8_t clamped;
|
|
4987
|
+
if constexpr (std::is_signed_v<integral_type_>) {
|
|
4988
|
+
clamped = value < integral_type_(0) ? std::uint8_t(0)
|
|
4989
|
+
: value > integral_type_(15) ? std::uint8_t(15)
|
|
4990
|
+
: static_cast<std::uint8_t>(value);
|
|
4991
|
+
}
|
|
4992
|
+
else { clamped = value > integral_type_(15) ? std::uint8_t(15) : static_cast<std::uint8_t>(value); }
|
|
4993
|
+
if (high_nibble_) *raw_ptr_ = static_cast<nk_u4x2_t>((*raw_ptr_ & 0x0F) | (clamped << 4));
|
|
4994
|
+
else *raw_ptr_ = static_cast<nk_u4x2_t>((*raw_ptr_ & 0xF0) | clamped);
|
|
4966
4995
|
return *this;
|
|
4967
4996
|
}
|
|
4997
|
+
constexpr sub_byte_ref &operator=(u8_t value) noexcept { return *this = value.raw(); }
|
|
4968
4998
|
};
|
|
4969
4999
|
|
|
4970
5000
|
/**
|
|
@@ -411,6 +411,9 @@ struct vector_view {
|
|
|
411
411
|
*/
|
|
412
412
|
template <typename value_type_>
|
|
413
413
|
struct vector_span {
|
|
414
|
+
static_assert(!std::is_const_v<value_type_>,
|
|
415
|
+
"vector_span requires a non-const value_type_; use vector_view<value_type_> for read-only access");
|
|
416
|
+
|
|
414
417
|
using value_type = value_type_;
|
|
415
418
|
using size_type = std::size_t;
|
|
416
419
|
using difference_type = std::ptrdiff_t;
|
|
@@ -451,12 +454,10 @@ struct vector_span {
|
|
|
451
454
|
}
|
|
452
455
|
|
|
453
456
|
/** @brief Raw byte pointer. */
|
|
454
|
-
constexpr char *byte_data() noexcept { return data_; }
|
|
455
|
-
constexpr char const *byte_data() const noexcept { return data_; }
|
|
457
|
+
constexpr char *byte_data() const noexcept { return data_; }
|
|
456
458
|
|
|
457
459
|
/** @brief Typed pointer (only valid if contiguous). */
|
|
458
|
-
constexpr value_type *data() noexcept { return reinterpret_cast<value_type *>(data_); }
|
|
459
|
-
constexpr value_type const *data() const noexcept { return reinterpret_cast<value_type const *>(data_); }
|
|
460
|
+
constexpr value_type *data() const noexcept { return reinterpret_cast<value_type *>(data_); }
|
|
460
461
|
|
|
461
462
|
/** @brief Implicit conversion to const view. */
|
|
462
463
|
constexpr operator vector_view<value_type>() const noexcept {
|
|
@@ -465,7 +466,7 @@ struct vector_span {
|
|
|
465
466
|
|
|
466
467
|
/** @brief Mutable integral indexing. Signed negatives wrap from end. */
|
|
467
468
|
template <std::integral index_type_>
|
|
468
|
-
decltype(auto) operator[](index_type_ idx) noexcept {
|
|
469
|
+
decltype(auto) operator[](index_type_ idx) const noexcept {
|
|
469
470
|
auto i = resolve_index_(idx, dimensions_);
|
|
470
471
|
if constexpr (dimensions_per_value<value_type>() > 1) {
|
|
471
472
|
constexpr auto dims_per_value = dimensions_per_value<value_type>();
|
|
@@ -479,37 +480,120 @@ struct vector_span {
|
|
|
479
480
|
else { return *reinterpret_cast<value_type *>(data_ + static_cast<difference_type>(i) * stride_bytes_); }
|
|
480
481
|
}
|
|
481
482
|
|
|
482
|
-
/** @brief Const integral indexing. Signed negatives wrap from end. */
|
|
483
|
-
template <std::integral index_type_>
|
|
484
|
-
decltype(auto) operator[](index_type_ idx) const noexcept {
|
|
485
|
-
return static_cast<vector_view<value_type>>(*this)[idx];
|
|
486
|
-
}
|
|
487
|
-
|
|
488
483
|
/** @brief Sub-slice via range. */
|
|
489
|
-
vector_span operator[](range r) noexcept {
|
|
484
|
+
vector_span operator[](range r) const noexcept {
|
|
490
485
|
size_type start, stop;
|
|
491
486
|
resolve_range_(r, dimensions_, start, stop);
|
|
492
487
|
auto count = range_extent_(start, stop, r.step);
|
|
493
488
|
return {data_ + static_cast<difference_type>(start) * stride_bytes_, count, stride_bytes_ * r.step};
|
|
494
489
|
}
|
|
495
490
|
|
|
496
|
-
/** @brief Const sub-slice via range. */
|
|
497
|
-
vector_view<value_type> operator[](range r) const noexcept {
|
|
498
|
-
return static_cast<vector_view<value_type>>(*this)[r];
|
|
499
|
-
}
|
|
500
|
-
|
|
501
491
|
/** @brief Select all elements. */
|
|
502
|
-
vector_span operator[](all_t) noexcept { return *this; }
|
|
492
|
+
vector_span operator[](all_t) const noexcept { return *this; }
|
|
503
493
|
|
|
504
|
-
/** @brief Dimension iterator to beginning. */
|
|
505
|
-
iterator begin() noexcept { return {*this, 0}; }
|
|
506
|
-
const_iterator begin() const noexcept { return {*this, 0}; }
|
|
494
|
+
/** @brief Dimension iterator to beginning. The handle is shallow-const, so iteration yields mutable refs. */
|
|
495
|
+
iterator begin() const noexcept { return {const_cast<vector_span &>(*this), 0}; }
|
|
507
496
|
const_iterator cbegin() const noexcept { return {*this, 0}; }
|
|
508
497
|
|
|
509
498
|
/** @brief Dimension iterator to end. */
|
|
510
|
-
iterator end() noexcept { return {*this, dimensions_}; }
|
|
511
|
-
const_iterator end() const noexcept { return {*this, dimensions_}; }
|
|
499
|
+
iterator end() const noexcept { return {const_cast<vector_span &>(*this), dimensions_}; }
|
|
512
500
|
const_iterator cend() const noexcept { return {*this, dimensions_}; }
|
|
501
|
+
|
|
502
|
+
/** @brief Zero-fill every element. memset on the contiguous fast path,
|
|
503
|
+
* one memset per element on the strided slow path. */
|
|
504
|
+
bool fill_zeros() noexcept {
|
|
505
|
+
static_assert(is_memset_zero_safe_v<value_type>,
|
|
506
|
+
"fill_zeros requires a dtype whose binary-zero is the value-zero");
|
|
507
|
+
if (data_ == nullptr || dimensions_ == 0) return true;
|
|
508
|
+
if constexpr (dimensions_per_value<value_type>() > 1) {
|
|
509
|
+
if (!is_contiguous()) return false;
|
|
510
|
+
auto byte_count = divide_round_up(dimensions_, dimensions_per_value<value_type>()) * sizeof(value_type);
|
|
511
|
+
std::memset(static_cast<void *>(data_), 0, byte_count);
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
if (is_contiguous()) {
|
|
516
|
+
std::memset(static_cast<void *>(data_), 0, dimensions_ * sizeof(value_type));
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
for (size_type element_index = 0; element_index < dimensions_; ++element_index)
|
|
520
|
+
std::memset(static_cast<void *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_),
|
|
521
|
+
0, sizeof(value_type));
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** @brief Fill every element with `value`. memset-then-overlay strategy mirroring the
|
|
527
|
+
* free `fill` on tensor_span: 1-byte storage uses a single byte_pattern memset, multi-byte
|
|
528
|
+
* storage uses a typed scalar broadcast loop. */
|
|
529
|
+
bool fill(value_type value) noexcept {
|
|
530
|
+
if (!fill_zeros()) return false;
|
|
531
|
+
value_type const default_value {};
|
|
532
|
+
if (std::memcmp(&value, &default_value, sizeof(value_type)) == 0) return true;
|
|
533
|
+
if constexpr (sizeof(value_type) == 1) {
|
|
534
|
+
unsigned char byte_pattern;
|
|
535
|
+
std::memcpy(&byte_pattern, &value, 1);
|
|
536
|
+
if constexpr (dimensions_per_value<value_type>() > 1) {
|
|
537
|
+
auto byte_count = divide_round_up(dimensions_, dimensions_per_value<value_type>()) * sizeof(value_type);
|
|
538
|
+
std::memset(static_cast<void *>(data_), byte_pattern, byte_count);
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
if (is_contiguous()) {
|
|
543
|
+
std::memset(static_cast<void *>(data_), byte_pattern, dimensions_ * sizeof(value_type));
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
for (size_type element_index = 0; element_index < dimensions_; ++element_index)
|
|
547
|
+
std::memset(
|
|
548
|
+
static_cast<void *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_),
|
|
549
|
+
byte_pattern, sizeof(value_type));
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
if (is_contiguous()) {
|
|
555
|
+
auto *typed_data = reinterpret_cast<value_type *>(data_);
|
|
556
|
+
for (size_type element_index = 0; element_index < dimensions_; ++element_index)
|
|
557
|
+
typed_data[element_index] = value;
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
for (size_type element_index = 0; element_index < dimensions_; ++element_index) {
|
|
561
|
+
auto *target =
|
|
562
|
+
reinterpret_cast<value_type *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_);
|
|
563
|
+
*target = value;
|
|
564
|
+
}
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** @brief Copy from a same-size view. memcpy on the contiguous fast path,
|
|
570
|
+
* per-element copy on the strided slow path. Returns false on size mismatch. */
|
|
571
|
+
bool copy_from(vector_view<value_type> input) noexcept {
|
|
572
|
+
if (input.size() != dimensions_) return false;
|
|
573
|
+
if (dimensions_ == 0) return true;
|
|
574
|
+
if constexpr (dimensions_per_value<value_type>() > 1) {
|
|
575
|
+
if (!is_contiguous() || !input.is_contiguous()) return false;
|
|
576
|
+
auto byte_count = divide_round_up(dimensions_, dimensions_per_value<value_type>()) * sizeof(value_type);
|
|
577
|
+
std::memcpy(static_cast<void *>(data_), static_cast<void const *>(input.byte_data()), byte_count);
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
if (is_contiguous() && input.is_contiguous()) {
|
|
582
|
+
std::memcpy(static_cast<void *>(data_), static_cast<void const *>(input.byte_data()),
|
|
583
|
+
dimensions_ * sizeof(value_type));
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
auto input_stride_bytes = input.stride_bytes();
|
|
587
|
+
for (size_type element_index = 0; element_index < dimensions_; ++element_index) {
|
|
588
|
+
auto *target =
|
|
589
|
+
reinterpret_cast<value_type *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_);
|
|
590
|
+
auto const *source = reinterpret_cast<value_type const *>(
|
|
591
|
+
input.byte_data() + static_cast<difference_type>(element_index) * input_stride_bytes);
|
|
592
|
+
*target = *source;
|
|
593
|
+
}
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
513
597
|
};
|
|
514
598
|
|
|
515
599
|
#pragma endregion Vector Span
|
|
@@ -751,6 +835,15 @@ struct vector {
|
|
|
751
835
|
iterator end() noexcept { return {*this, dimensions_}; }
|
|
752
836
|
const_iterator end() const noexcept { return {*this, dimensions_}; }
|
|
753
837
|
const_iterator cend() const noexcept { return {*this, dimensions_}; }
|
|
838
|
+
|
|
839
|
+
/** @brief Zero-fill every element. */
|
|
840
|
+
bool fill_zeros() noexcept { return span().fill_zeros(); }
|
|
841
|
+
|
|
842
|
+
/** @brief Fill every element with `value`. */
|
|
843
|
+
bool fill(value_type value) noexcept { return span().fill(value); }
|
|
844
|
+
|
|
845
|
+
/** @brief Copy from a same-size view. */
|
|
846
|
+
bool copy_from(vector_view<value_type> input) noexcept { return span().copy_from(input); }
|
|
754
847
|
};
|
|
755
848
|
|
|
756
849
|
/** @brief Non-member swap. */
|
|
@@ -31,15 +31,18 @@ This binding stays centered on the vector families it actually exports.
|
|
|
31
31
|
|
|
32
32
|
## Ecosystem Comparison
|
|
33
33
|
|
|
34
|
-
| Feature | NumKong | [mathjs]
|
|
35
|
-
| ---------------------------- | -------------------------------------------------------------- |
|
|
36
|
-
| Operation families | dots, distances, binary, probability, cast, packed, symmetric | general arithmetic, matrix ops, statistics
|
|
37
|
-
| Precision | BFloat16 through sub-byte; automatic widening; Kahan summation | Float64 only; standard accuracy
|
|
38
|
-
| Runtime SIMD dispatch | auto-selects best ISA per-thread across x86, ARM, RISC-V | none; pure JS
|
|
39
|
-
| Packed matrix, GEMM-like | `dotsPack` + `dotsPacked`; persistent packing; amortized | `math.multiply` — no persistent packing
|
|
40
|
-
| Symmetric kernels, SYRK-like | `dotsSymmetric`; upper triangle only; row-range partitioning | no duplicate-pair skipping
|
|
41
|
-
| WASM fallback | yes — portable, runs in browser without native addon | yes — pure JS, no native required
|
|
42
|
-
| Bundle size | small | moderate
|
|
34
|
+
| Feature | NumKong | [mathjs][mathjs] | [tensorflow.js][tensorflow-js] |
|
|
35
|
+
| ---------------------------- | -------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------- |
|
|
36
|
+
| Operation families | dots, distances, binary, probability, cast, packed, symmetric | general arithmetic, matrix ops, statistics | matmul, elementwise, reductions |
|
|
37
|
+
| Precision | BFloat16 through sub-byte; automatic widening; Kahan summation | Float64 only; standard accuracy | Float32 primarily; no sub-byte; standard accuracy |
|
|
38
|
+
| Runtime SIMD dispatch | auto-selects best ISA per-thread across x86, ARM, RISC-V | none; pure JS | fixed at build time via WASM SIMD or WebGL |
|
|
39
|
+
| Packed matrix, GEMM-like | `dotsPack` + `dotsPacked`; persistent packing; amortized | `math.multiply` — no persistent packing | `tf.matMul` — no persistent packing |
|
|
40
|
+
| Symmetric kernels, SYRK-like | `dotsSymmetric`; upper triangle only; row-range partitioning | no duplicate-pair skipping | no duplicate-pair skipping |
|
|
41
|
+
| WASM fallback | yes — portable, runs in browser without native addon | yes — pure JS, no native required | yes — also WebGL/WebGPU |
|
|
42
|
+
| Bundle size | small | moderate | large |
|
|
43
|
+
|
|
44
|
+
[mathjs]: https://github.com/josdejong/mathjs
|
|
45
|
+
[tensorflow-js]: https://github.com/tensorflow/tfjs
|
|
43
46
|
|
|
44
47
|
## Installation
|
|
45
48
|
|
|
@@ -74,11 +77,7 @@ For self-hosted WASM, download the binaries from a [GitHub Release](https://gith
|
|
|
74
77
|
|
|
75
78
|
```html
|
|
76
79
|
<script type="module">
|
|
77
|
-
import * as numkong from "./numkong
|
|
78
|
-
import NumKongModule from "./numkong.js";
|
|
79
|
-
|
|
80
|
-
const wasm = await NumKongModule();
|
|
81
|
-
numkong.initWasm(wasm);
|
|
80
|
+
import * as numkong from "./numkong.js";
|
|
82
81
|
|
|
83
82
|
const a = new Float32Array([1, 2, 3]);
|
|
84
83
|
const b = new Float32Array([4, 5, 6]);
|
|
@@ -86,6 +85,9 @@ For self-hosted WASM, download the binaries from a [GitHub Release](https://gith
|
|
|
86
85
|
</script>
|
|
87
86
|
```
|
|
88
87
|
|
|
88
|
+
The release archive ships `numkong.js`, `numkong-emscripten.js`, and `numkong.wasm`.
|
|
89
|
+
Only `numkong.js` is imported — it locates the other two next to itself and instantiates the module before any export is used.
|
|
90
|
+
|
|
89
91
|
Or import the subpath from a bundler or Node.js (without the native addon):
|
|
90
92
|
|
|
91
93
|
```js
|
|
@@ -186,11 +188,11 @@ console.log(a8.byteLength);
|
|
|
186
188
|
|
|
187
189
|
If the underlying storage is a raw `Uint16Array` or `Uint8Array`, JS itself cannot know whether those bytes mean integers, `f16`, `bf16`, mini-floats, or packed bits.
|
|
188
190
|
That is exactly when the `DType` tag becomes mandatory.
|
|
189
|
-
|
|
191
|
+
`cast` converts many values between all supported types, naming both sides by their dtype string:
|
|
190
192
|
|
|
191
193
|
```ts
|
|
192
194
|
numkong.cast(f32Source, "f32", bf16Dest, "bf16");
|
|
193
|
-
numkong.cast(f32Source, "f32",
|
|
195
|
+
numkong.cast(f32Source, "f32", e4m3Dest, "e4m3");
|
|
194
196
|
```
|
|
195
197
|
|
|
196
198
|
## Vector Views and Owned Buffers
|
|
@@ -9,16 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
#include <string.h> // `strcmp` function
|
|
11
11
|
|
|
12
|
-
#if defined(NK_USE_OPENMP)
|
|
13
|
-
#include <omp.h>
|
|
14
|
-
#endif
|
|
15
|
-
|
|
16
12
|
#include <node_api.h> // `napi_*` functions — N-API v6+ for BigInt (Node ≥ 10.20)
|
|
17
13
|
|
|
18
14
|
#include <numkong/numkong.h> // `nk_*` functions — must be first to bring `_GNU_SOURCE`
|
|
19
15
|
|
|
20
|
-
#
|
|
21
|
-
#define NK_PARALLEL_SYMMETRIC_TILE 32
|
|
16
|
+
#include "parallel.h" // `nk_parallel_for_tiles`, tile sizes
|
|
22
17
|
|
|
23
18
|
/** @brief Global variable that caches the CPU capabilities, and is computed just once, when the module is loaded. */
|
|
24
19
|
nk_capability_t static_capabilities = nk_cap_serial_k;
|
|
@@ -484,6 +479,28 @@ static napi_value api_dots_pack(napi_env env, napi_callback_info info) {
|
|
|
484
479
|
return result_obj;
|
|
485
480
|
}
|
|
486
481
|
|
|
482
|
+
/** @brief One tile of rows of C = A × Bᵀ with B pre-packed. */
|
|
483
|
+
typedef struct packed_task_t {
|
|
484
|
+
nk_dots_packed_punned_t kernel;
|
|
485
|
+
char const *a;
|
|
486
|
+
void const *b_packed;
|
|
487
|
+
char *c;
|
|
488
|
+
nk_size_t rows;
|
|
489
|
+
nk_size_t columns;
|
|
490
|
+
nk_size_t depth;
|
|
491
|
+
nk_size_t a_stride_bytes;
|
|
492
|
+
nk_size_t c_stride_bytes;
|
|
493
|
+
} packed_task_t;
|
|
494
|
+
|
|
495
|
+
static void packed_tile_(nk_size_t tile_index, void *context) {
|
|
496
|
+
packed_task_t const *task = (packed_task_t const *)context;
|
|
497
|
+
nk_size_t const row = tile_index * NK_PARALLEL_PACKED_TILE;
|
|
498
|
+
nk_size_t const chunk = (row + NK_PARALLEL_PACKED_TILE <= task->rows) ? NK_PARALLEL_PACKED_TILE
|
|
499
|
+
: (task->rows - row);
|
|
500
|
+
task->kernel(task->a + row * task->a_stride_bytes, task->b_packed, task->c + row * task->c_stride_bytes, chunk,
|
|
501
|
+
task->columns, task->depth, task->a_stride_bytes, task->c_stride_bytes);
|
|
502
|
+
}
|
|
503
|
+
|
|
487
504
|
/**
|
|
488
505
|
* @brief Shared dispatcher for packed operations (dots, angulars, euclideans).
|
|
489
506
|
* Args: TypedArray a, ArrayBuffer packed, TypedArray result, numbers height/width/depth/aStride/resultStride, string
|
|
@@ -544,23 +561,17 @@ static napi_value api_packed_common(napi_env env, napi_callback_info info, nk_ke
|
|
|
544
561
|
uint32_t threads = 1;
|
|
545
562
|
if (argc == 10) napi_get_value_uint32(env, args[9], &threads);
|
|
546
563
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
for (tile_idx = 0; tile_idx < tile_count; tile_idx++) {
|
|
559
|
-
nk_size_t row = (nk_size_t)tile_idx * NK_PARALLEL_PACKED_TILE;
|
|
560
|
-
nk_size_t chunk = (row + NK_PARALLEL_PACKED_TILE <= height) ? NK_PARALLEL_PACKED_TILE : (height - row);
|
|
561
|
-
kernel((char const *)a_data + row * a_stride, packed_data, (char *)result_data + row * result_stride, chunk,
|
|
562
|
-
(nk_size_t)width, (nk_size_t)depth, (nk_size_t)a_stride, (nk_size_t)result_stride);
|
|
563
|
-
}
|
|
564
|
+
packed_task_t task;
|
|
565
|
+
task.kernel = kernel;
|
|
566
|
+
task.a = a_data;
|
|
567
|
+
task.b_packed = packed_data;
|
|
568
|
+
task.c = result_data;
|
|
569
|
+
task.rows = height;
|
|
570
|
+
task.columns = width;
|
|
571
|
+
task.depth = depth;
|
|
572
|
+
task.a_stride_bytes = a_stride;
|
|
573
|
+
task.c_stride_bytes = result_stride;
|
|
574
|
+
nk_parallel_for_tiles(nk_size_divide_round_up_(height, NK_PARALLEL_PACKED_TILE), threads, packed_tile_, &task);
|
|
564
575
|
return NULL;
|
|
565
576
|
}
|
|
566
577
|
|
|
@@ -574,6 +585,29 @@ static napi_value api_euclideans_packed(napi_env env, napi_callback_info info) {
|
|
|
574
585
|
return api_packed_common(env, info, nk_kernel_euclideans_packed_k);
|
|
575
586
|
}
|
|
576
587
|
|
|
588
|
+
/** @brief One tile of rows of C = A × Aᵀ. */
|
|
589
|
+
typedef struct symmetric_task_t {
|
|
590
|
+
nk_dots_symmetric_punned_t kernel;
|
|
591
|
+
void const *vectors;
|
|
592
|
+
void *result;
|
|
593
|
+
nk_size_t vectors_count;
|
|
594
|
+
nk_size_t depth;
|
|
595
|
+
nk_size_t stride_bytes;
|
|
596
|
+
nk_size_t result_stride_bytes;
|
|
597
|
+
nk_size_t row_start;
|
|
598
|
+
nk_size_t row_end;
|
|
599
|
+
} symmetric_task_t;
|
|
600
|
+
|
|
601
|
+
static void symmetric_tile_(nk_size_t tile_index, void *context) {
|
|
602
|
+
symmetric_task_t const *task = (symmetric_task_t const *)context;
|
|
603
|
+
nk_size_t const tile_start = task->row_start + tile_index * NK_PARALLEL_SYMMETRIC_TILE;
|
|
604
|
+
nk_size_t const tile_rows = (tile_start + NK_PARALLEL_SYMMETRIC_TILE <= task->row_end)
|
|
605
|
+
? NK_PARALLEL_SYMMETRIC_TILE
|
|
606
|
+
: (task->row_end - tile_start);
|
|
607
|
+
task->kernel(task->vectors, task->vectors_count, task->depth, task->stride_bytes, task->result,
|
|
608
|
+
task->result_stride_bytes, tile_start, tile_rows);
|
|
609
|
+
}
|
|
610
|
+
|
|
577
611
|
/**
|
|
578
612
|
* @brief Shared dispatcher for symmetric operations (dots, angulars, euclideans).
|
|
579
613
|
* Args: TypedArray vectors, TypedArray result, numbers nVectors/depth/vectorsStride/resultStride/rowStart/rowCount,
|
|
@@ -630,23 +664,19 @@ static napi_value api_symmetric_common(napi_env env, napi_callback_info info, nk
|
|
|
630
664
|
uint32_t threads = 1;
|
|
631
665
|
if (argc == 10) napi_get_value_uint32(env, args[9], &threads);
|
|
632
666
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
: ((nk_size_t)row_start + row_count - tile_start);
|
|
647
|
-
kernel(vectors_data, (nk_size_t)n_vectors, (nk_size_t)depth, (nk_size_t)vectors_stride, result_data,
|
|
648
|
-
(nk_size_t)result_stride, tile_start, tile_rows);
|
|
649
|
-
}
|
|
667
|
+
symmetric_task_t task;
|
|
668
|
+
task.kernel = kernel;
|
|
669
|
+
task.vectors = vectors_data;
|
|
670
|
+
task.result = result_data;
|
|
671
|
+
task.vectors_count = n_vectors;
|
|
672
|
+
task.depth = depth;
|
|
673
|
+
task.stride_bytes = vectors_stride;
|
|
674
|
+
task.result_stride_bytes = result_stride;
|
|
675
|
+
task.row_start = row_start;
|
|
676
|
+
// Widen before the sum so two `uint32_t` row bounds cannot wrap.
|
|
677
|
+
task.row_end = (nk_size_t)row_start + row_count;
|
|
678
|
+
nk_parallel_for_tiles(nk_size_divide_round_up_(row_count, NK_PARALLEL_SYMMETRIC_TILE), threads, symmetric_tile_,
|
|
679
|
+
&task);
|
|
650
680
|
|
|
651
681
|
return NULL;
|
|
652
682
|
}
|