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
|
@@ -763,7 +763,7 @@ NK_PUBLIC void nk_umeyama_f32_haswell(nk_f32_t const *a, nk_f32_t const *b, nk_s
|
|
|
763
763
|
optimal_rotation[3] = 0.0, optimal_rotation[4] = 1.0, optimal_rotation[5] = 0.0;
|
|
764
764
|
optimal_rotation[6] = 0.0, optimal_rotation[7] = 0.0, optimal_rotation[8] = 1.0;
|
|
765
765
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
766
|
-
applied_scale =
|
|
766
|
+
applied_scale = trace_rotation_covariance / centered_norm_squared_a;
|
|
767
767
|
}
|
|
768
768
|
else {
|
|
769
769
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -792,7 +792,7 @@ NK_PUBLIC void nk_umeyama_f32_haswell(nk_f32_t const *a, nk_f32_t const *b, nk_s
|
|
|
792
792
|
optimal_rotation[8] = svd_right[6] * svd_left[6] + svd_right[7] * svd_left[7] + svd_right[8] * svd_left[8];
|
|
793
793
|
}
|
|
794
794
|
nk_f64_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
|
|
795
|
-
applied_scale =
|
|
795
|
+
applied_scale = trace_ds / centered_norm_squared_a;
|
|
796
796
|
trace_rotation_covariance =
|
|
797
797
|
optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
|
|
798
798
|
optimal_rotation[2] * cross_covariance[6] + optimal_rotation[3] * cross_covariance[1] +
|
|
@@ -962,7 +962,7 @@ NK_PUBLIC void nk_umeyama_f64_haswell(nk_f64_t const *a, nk_f64_t const *b, nk_s
|
|
|
962
962
|
optimal_rotation[3] = 0.0, optimal_rotation[4] = 1.0, optimal_rotation[5] = 0.0;
|
|
963
963
|
optimal_rotation[6] = 0.0, optimal_rotation[7] = 0.0, optimal_rotation[8] = 1.0;
|
|
964
964
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
965
|
-
c =
|
|
965
|
+
c = trace_rotation_covariance / centered_norm_squared_a;
|
|
966
966
|
}
|
|
967
967
|
else {
|
|
968
968
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -972,7 +972,7 @@ NK_PUBLIC void nk_umeyama_f64_haswell(nk_f64_t const *a, nk_f64_t const *b, nk_s
|
|
|
972
972
|
nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
|
|
973
973
|
nk_f64_t d3 = det < 0 ? -1.0 : 1.0;
|
|
974
974
|
nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8], d3);
|
|
975
|
-
c =
|
|
975
|
+
c = trace_ds / centered_norm_squared_a;
|
|
976
976
|
|
|
977
977
|
if (det < 0) {
|
|
978
978
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
@@ -1633,7 +1633,7 @@ NK_PUBLIC void nk_umeyama_f16_haswell(nk_f16_t const *a, nk_f16_t const *b, nk_s
|
|
|
1633
1633
|
optimal_rotation[3] = 0.0f, optimal_rotation[4] = 1.0f, optimal_rotation[5] = 0.0f;
|
|
1634
1634
|
optimal_rotation[6] = 0.0f, optimal_rotation[7] = 0.0f, optimal_rotation[8] = 1.0f;
|
|
1635
1635
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
1636
|
-
applied_scale =
|
|
1636
|
+
applied_scale = trace_rotation_covariance / centered_norm_squared_a;
|
|
1637
1637
|
}
|
|
1638
1638
|
else {
|
|
1639
1639
|
nk_f32_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -1664,7 +1664,7 @@ NK_PUBLIC void nk_umeyama_f16_haswell(nk_f16_t const *a, nk_f16_t const *b, nk_s
|
|
|
1664
1664
|
optimal_rotation[8] = svd_right[6] * svd_left[6] + svd_right[7] * svd_left[7] + svd_right[8] * svd_left[8];
|
|
1665
1665
|
}
|
|
1666
1666
|
nk_f32_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
|
|
1667
|
-
applied_scale =
|
|
1667
|
+
applied_scale = trace_ds / centered_norm_squared_a;
|
|
1668
1668
|
trace_rotation_covariance =
|
|
1669
1669
|
optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
|
|
1670
1670
|
optimal_rotation[2] * cross_covariance[6] + optimal_rotation[3] * cross_covariance[1] +
|
|
@@ -1819,7 +1819,7 @@ NK_PUBLIC void nk_umeyama_bf16_haswell(nk_bf16_t const *a, nk_bf16_t const *b, n
|
|
|
1819
1819
|
optimal_rotation[3] = 0.0f, optimal_rotation[4] = 1.0f, optimal_rotation[5] = 0.0f;
|
|
1820
1820
|
optimal_rotation[6] = 0.0f, optimal_rotation[7] = 0.0f, optimal_rotation[8] = 1.0f;
|
|
1821
1821
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
1822
|
-
applied_scale =
|
|
1822
|
+
applied_scale = trace_rotation_covariance / centered_norm_squared_a;
|
|
1823
1823
|
}
|
|
1824
1824
|
else {
|
|
1825
1825
|
nk_f32_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -1850,7 +1850,7 @@ NK_PUBLIC void nk_umeyama_bf16_haswell(nk_bf16_t const *a, nk_bf16_t const *b, n
|
|
|
1850
1850
|
optimal_rotation[8] = svd_right[6] * svd_left[6] + svd_right[7] * svd_left[7] + svd_right[8] * svd_left[8];
|
|
1851
1851
|
}
|
|
1852
1852
|
nk_f32_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
|
|
1853
|
-
applied_scale =
|
|
1853
|
+
applied_scale = trace_ds / centered_norm_squared_a;
|
|
1854
1854
|
trace_rotation_covariance =
|
|
1855
1855
|
optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
|
|
1856
1856
|
optimal_rotation[2] * cross_covariance[6] + optimal_rotation[3] * cross_covariance[1] +
|
|
@@ -853,7 +853,7 @@ NK_PUBLIC void nk_umeyama_f32_neon(nk_f32_t const *a, nk_f32_t const *b, nk_size
|
|
|
853
853
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
854
854
|
optimal_rotation[8] = 1;
|
|
855
855
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
856
|
-
applied_scale =
|
|
856
|
+
applied_scale = trace_rotation_covariance / centered_norm_squared_a;
|
|
857
857
|
}
|
|
858
858
|
else {
|
|
859
859
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -884,7 +884,7 @@ NK_PUBLIC void nk_umeyama_f32_neon(nk_f32_t const *a, nk_f32_t const *b, nk_size
|
|
|
884
884
|
}
|
|
885
885
|
|
|
886
886
|
nk_f64_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
|
|
887
|
-
applied_scale =
|
|
887
|
+
applied_scale = trace_ds / centered_norm_squared_a;
|
|
888
888
|
|
|
889
889
|
trace_rotation_covariance =
|
|
890
890
|
optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
|
|
@@ -1139,7 +1139,7 @@ NK_PUBLIC void nk_umeyama_f64_neon(nk_f64_t const *a, nk_f64_t const *b, nk_size
|
|
|
1139
1139
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
1140
1140
|
optimal_rotation[8] = 1;
|
|
1141
1141
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
1142
|
-
computed_scale =
|
|
1142
|
+
computed_scale = trace_rotation_covariance / centered_norm_squared_a;
|
|
1143
1143
|
}
|
|
1144
1144
|
else {
|
|
1145
1145
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -1149,7 +1149,7 @@ NK_PUBLIC void nk_umeyama_f64_neon(nk_f64_t const *a, nk_f64_t const *b, nk_size
|
|
|
1149
1149
|
// Handle reflection and compute scale
|
|
1150
1150
|
nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
|
|
1151
1151
|
nk_f64_t trace_d_s = svd_diagonal[0] + svd_diagonal[4] + (det < 0 ? -svd_diagonal[8] : svd_diagonal[8]);
|
|
1152
|
-
computed_scale =
|
|
1152
|
+
computed_scale = trace_d_s / centered_norm_squared_a;
|
|
1153
1153
|
|
|
1154
1154
|
if (det < 0) {
|
|
1155
1155
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
@@ -1679,7 +1679,7 @@ NK_PUBLIC void nk_umeyama_f16_neon(nk_f16_t const *a, nk_f16_t const *b, nk_size
|
|
|
1679
1679
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
1680
1680
|
optimal_rotation[8] = 1;
|
|
1681
1681
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
1682
|
-
scale_factor =
|
|
1682
|
+
scale_factor = trace_rotation_covariance / centered_norm_squared_a;
|
|
1683
1683
|
}
|
|
1684
1684
|
else {
|
|
1685
1685
|
// SVD of H = U * S * Vᵀ
|
|
@@ -1701,7 +1701,7 @@ NK_PUBLIC void nk_umeyama_f16_neon(nk_f16_t const *a, nk_f16_t const *b, nk_size
|
|
|
1701
1701
|
nk_f32_t rotation_determinant = nk_det3x3_f32_(optimal_rotation);
|
|
1702
1702
|
nk_f32_t sign_det = rotation_determinant < 0 ? -1.0f : 1.0f;
|
|
1703
1703
|
nk_f32_t trace_scaled_s = svd_diagonal[0] + svd_diagonal[4] + sign_det * svd_diagonal[8];
|
|
1704
|
-
scale_factor =
|
|
1704
|
+
scale_factor = trace_scaled_s / centered_norm_squared_a;
|
|
1705
1705
|
|
|
1706
1706
|
if (rotation_determinant < 0) {
|
|
1707
1707
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
#include "numkong/types.h"
|
|
41
41
|
#include "numkong/cast/neon.h" // `nk_u16x8_splat_`
|
|
42
42
|
#include "numkong/spatial/neon.h" // `nk_f32_sqrt_neon`
|
|
43
|
+
#include "numkong/mesh/serial.h" // `nk_det3x3_f32_`, `nk_svd3x3_f32_`
|
|
43
44
|
|
|
44
45
|
#if defined(__cplusplus)
|
|
45
46
|
extern "C" {
|
|
@@ -559,7 +560,7 @@ NK_PUBLIC void nk_umeyama_bf16_neonbfdot(nk_bf16_t const *a, nk_bf16_t const *b,
|
|
|
559
560
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
560
561
|
optimal_rotation[8] = 1;
|
|
561
562
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
562
|
-
c =
|
|
563
|
+
c = trace_rotation_covariance / centered_norm_squared_a;
|
|
563
564
|
}
|
|
564
565
|
else {
|
|
565
566
|
nk_f32_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -581,7 +582,7 @@ NK_PUBLIC void nk_umeyama_bf16_neonbfdot(nk_bf16_t const *a, nk_bf16_t const *b,
|
|
|
581
582
|
nk_f32_t rotation_det = nk_det3x3_f32_(optimal_rotation);
|
|
582
583
|
nk_f32_t sign_det = rotation_det < 0 ? -1.0f : 1.0f;
|
|
583
584
|
nk_f32_t trace_scaled_s = svd_diagonal[0] + svd_diagonal[4] + sign_det * svd_diagonal[8];
|
|
584
|
-
c =
|
|
585
|
+
c = trace_scaled_s / centered_norm_squared_a;
|
|
585
586
|
|
|
586
587
|
if (rotation_det < 0) {
|
|
587
588
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
#include "numkong/types.h"
|
|
40
40
|
#include "numkong/spatial/neon.h" // `nk_f32_sqrt_neon`
|
|
41
|
+
#include "numkong/mesh/serial.h" // `nk_det3x3_f32_`, `nk_svd3x3_f32_`
|
|
41
42
|
|
|
42
43
|
#if defined(__cplusplus)
|
|
43
44
|
extern "C" {
|
|
@@ -604,7 +605,7 @@ NK_PUBLIC void nk_umeyama_f16_neonfhm(nk_f16_t const *a, nk_f16_t const *b, nk_s
|
|
|
604
605
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
605
606
|
optimal_rotation[8] = 1;
|
|
606
607
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
607
|
-
scale_factor =
|
|
608
|
+
scale_factor = trace_rotation_covariance / centered_norm_squared_a;
|
|
608
609
|
}
|
|
609
610
|
else {
|
|
610
611
|
// SVD of H = U · S · Vᵀ
|
|
@@ -626,7 +627,7 @@ NK_PUBLIC void nk_umeyama_f16_neonfhm(nk_f16_t const *a, nk_f16_t const *b, nk_s
|
|
|
626
627
|
nk_f32_t rotation_determinant = nk_det3x3_f32_(optimal_rotation);
|
|
627
628
|
nk_f32_t sign_det = rotation_determinant < 0 ? -1.0f : 1.0f;
|
|
628
629
|
nk_f32_t trace_scaled_s = svd_diagonal[0] + svd_diagonal[4] + sign_det * svd_diagonal[8];
|
|
629
|
-
scale_factor =
|
|
630
|
+
scale_factor = trace_scaled_s / centered_norm_squared_a;
|
|
630
631
|
|
|
631
632
|
if (rotation_determinant < 0) {
|
|
632
633
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
@@ -884,7 +884,7 @@ NK_PUBLIC void nk_umeyama_f32_rvv(nk_f32_t const *a, nk_f32_t const *b, nk_size_
|
|
|
884
884
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
885
885
|
optimal_rotation[8] = 1;
|
|
886
886
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
887
|
-
scale_factor =
|
|
887
|
+
scale_factor = trace_rotation_covariance / centered_norm_squared_a;
|
|
888
888
|
}
|
|
889
889
|
else {
|
|
890
890
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -894,7 +894,7 @@ NK_PUBLIC void nk_umeyama_f32_rvv(nk_f32_t const *a, nk_f32_t const *b, nk_size_
|
|
|
894
894
|
nk_f64_t sign_det = det < 0 ? -1.0 : 1.0;
|
|
895
895
|
nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8],
|
|
896
896
|
sign_det);
|
|
897
|
-
scale_factor =
|
|
897
|
+
scale_factor = trace_ds / centered_norm_squared_a;
|
|
898
898
|
if (det < 0) {
|
|
899
899
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
900
900
|
nk_rotation_from_svd_f64_serial_(svd_left, svd_right, optimal_rotation);
|
|
@@ -944,7 +944,7 @@ NK_PUBLIC void nk_umeyama_f64_rvv(nk_f64_t const *a, nk_f64_t const *b, nk_size_
|
|
|
944
944
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
945
945
|
optimal_rotation[8] = 1;
|
|
946
946
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
947
|
-
scale_factor =
|
|
947
|
+
scale_factor = trace_rotation_covariance / centered_norm_squared_a;
|
|
948
948
|
}
|
|
949
949
|
else {
|
|
950
950
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -954,7 +954,7 @@ NK_PUBLIC void nk_umeyama_f64_rvv(nk_f64_t const *a, nk_f64_t const *b, nk_size_
|
|
|
954
954
|
nk_f64_t sign_det = det < 0 ? -1.0 : 1.0;
|
|
955
955
|
nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8],
|
|
956
956
|
sign_det);
|
|
957
|
-
scale_factor =
|
|
957
|
+
scale_factor = trace_ds / centered_norm_squared_a;
|
|
958
958
|
if (det < 0) {
|
|
959
959
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
960
960
|
nk_rotation_from_svd_f64_serial_(svd_left, svd_right, optimal_rotation);
|
|
@@ -272,17 +272,6 @@ extern "C" {
|
|
|
272
272
|
m[2] * (m[3] * m[7] - m[4] * m[6]); \
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
/* Keep the serial instantiations below actually scalar, regardless of build type.
|
|
276
|
-
* Without this, -O3 + LTO can vectorize or clone the serial kernels under AVX-512
|
|
277
|
-
* callers in dispatch_*.c, which wastes binary and breaks the nk_*_serial-as-scalar-oracle
|
|
278
|
-
* contract that tests and numerical-stability docs rely on. See dots/serial.h. */
|
|
279
|
-
#if defined(__clang__)
|
|
280
|
-
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
281
|
-
#elif defined(__GNUC__)
|
|
282
|
-
#pragma GCC push_options
|
|
283
|
-
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
284
|
-
#endif
|
|
285
|
-
|
|
286
275
|
/* Size bias for release. Gated on NDEBUG so Debug builds keep -O0 for stepping. */
|
|
287
276
|
#if defined(NDEBUG)
|
|
288
277
|
#if defined(_MSC_VER)
|
|
@@ -701,6 +690,17 @@ nk_define_det3x3_(f64)
|
|
|
701
690
|
*result = (nk_##result_type##_t)compute_sqrt((sum_squared + sum_squared_compensation) * inv_n); \
|
|
702
691
|
}
|
|
703
692
|
|
|
693
|
+
/* Keep the serial instantiations below actually scalar, regardless of build type.
|
|
694
|
+
* Without this, -O3 + LTO can vectorize or clone the serial kernels under AVX-512
|
|
695
|
+
* callers in dispatch_*.c, which wastes binary and breaks the nk_*_serial-as-scalar-oracle
|
|
696
|
+
* contract that tests and numerical-stability docs rely on. See dots/serial.h. */
|
|
697
|
+
#if defined(__clang__)
|
|
698
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
699
|
+
#elif defined(__GNUC__)
|
|
700
|
+
#pragma GCC push_options
|
|
701
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
702
|
+
#endif
|
|
703
|
+
|
|
704
704
|
nk_define_rmsd_(f64, f64, f64, f64, nk_assign_from_to_, nk_f64_sqrt_serial) // nk_rmsd_f64_serial
|
|
705
705
|
nk_define_kabsch_(f64, f64, f64, f64, f64, nk_assign_from_to_, nk_f64_sqrt_serial) // nk_kabsch_f64_serial
|
|
706
706
|
nk_define_umeyama_(f64, f64, f64, f64, f64, nk_assign_from_to_, nk_f64_sqrt_serial) // nk_umeyama_f64_serial
|
|
@@ -740,21 +740,21 @@ nk_define_umeyama_(bf16, f32, f32, f32, f32, nk_bf16_to_f32_serial, nk_f32_sqrt_
|
|
|
740
740
|
#undef nk_define_kabsch_
|
|
741
741
|
#undef nk_define_umeyama_
|
|
742
742
|
|
|
743
|
-
#if defined(
|
|
744
|
-
#if defined(_MSC_VER)
|
|
745
|
-
#pragma optimize("", on)
|
|
746
|
-
#elif defined(__clang__)
|
|
743
|
+
#if defined(__clang__)
|
|
747
744
|
#pragma clang attribute pop
|
|
748
745
|
#elif defined(__GNUC__)
|
|
749
746
|
#pragma GCC pop_options
|
|
750
747
|
#endif
|
|
751
|
-
#endif
|
|
752
748
|
|
|
753
|
-
#if defined(
|
|
749
|
+
#if defined(NDEBUG)
|
|
750
|
+
#if defined(_MSC_VER)
|
|
751
|
+
#pragma optimize("", on)
|
|
752
|
+
#elif defined(__clang__)
|
|
754
753
|
#pragma clang attribute pop
|
|
755
754
|
#elif defined(__GNUC__)
|
|
756
755
|
#pragma GCC pop_options
|
|
757
756
|
#endif
|
|
757
|
+
#endif
|
|
758
758
|
|
|
759
759
|
#if defined(__cplusplus)
|
|
760
760
|
} // extern "C"
|
|
@@ -1157,7 +1157,7 @@ NK_PUBLIC void nk_umeyama_f64_skylake(nk_f64_t const *a, nk_f64_t const *b, nk_s
|
|
|
1157
1157
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
1158
1158
|
optimal_rotation[8] = 1;
|
|
1159
1159
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
1160
|
-
c =
|
|
1160
|
+
c = trace_rotation_covariance / centered_norm_squared_a;
|
|
1161
1161
|
}
|
|
1162
1162
|
else {
|
|
1163
1163
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -1168,7 +1168,7 @@ NK_PUBLIC void nk_umeyama_f64_skylake(nk_f64_t const *a, nk_f64_t const *b, nk_s
|
|
|
1168
1168
|
nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
|
|
1169
1169
|
nk_f64_t d3 = det < 0 ? -1.0 : 1.0;
|
|
1170
1170
|
nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8], d3);
|
|
1171
|
-
c =
|
|
1171
|
+
c = trace_ds / centered_norm_squared_a;
|
|
1172
1172
|
|
|
1173
1173
|
if (det < 0) {
|
|
1174
1174
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
@@ -1620,7 +1620,7 @@ NK_PUBLIC void nk_umeyama_f16_skylake(nk_f16_t const *a, nk_f16_t const *b, nk_s
|
|
|
1620
1620
|
nk_f32_t det = nk_det3x3_f32_(optimal_rotation);
|
|
1621
1621
|
nk_f32_t d3 = det < 0.0f ? -1.0f : 1.0f;
|
|
1622
1622
|
nk_f32_t trace_ds = nk_sum_three_products_f32_(svd_diagonal[0], 1.0f, svd_diagonal[4], 1.0f, svd_diagonal[8], d3);
|
|
1623
|
-
nk_f32_t c =
|
|
1623
|
+
nk_f32_t c = trace_ds / centered_norm_squared_a;
|
|
1624
1624
|
if (scale) *scale = c;
|
|
1625
1625
|
|
|
1626
1626
|
if (det < 0.0f) {
|
|
@@ -1749,7 +1749,7 @@ NK_PUBLIC void nk_umeyama_bf16_skylake(nk_bf16_t const *a, nk_bf16_t const *b, n
|
|
|
1749
1749
|
nk_f32_t det = nk_det3x3_f32_(optimal_rotation);
|
|
1750
1750
|
nk_f32_t d3 = det < 0.0f ? -1.0f : 1.0f;
|
|
1751
1751
|
nk_f32_t trace_ds = nk_sum_three_products_f32_(svd_diagonal[0], 1.0f, svd_diagonal[4], 1.0f, svd_diagonal[8], d3);
|
|
1752
|
-
nk_f32_t c =
|
|
1752
|
+
nk_f32_t c = trace_ds / centered_norm_squared_a;
|
|
1753
1753
|
if (scale) *scale = c;
|
|
1754
1754
|
|
|
1755
1755
|
if (det < 0.0f) {
|
|
@@ -842,7 +842,7 @@ NK_PUBLIC void nk_umeyama_f32_v128relaxed(nk_f32_t const *a, nk_f32_t const *b,
|
|
|
842
842
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
843
843
|
optimal_rotation[8] = 1;
|
|
844
844
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
845
|
-
computed_scale =
|
|
845
|
+
computed_scale = trace_rotation_covariance / centered_norm_squared_a;
|
|
846
846
|
}
|
|
847
847
|
else {
|
|
848
848
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -874,7 +874,7 @@ NK_PUBLIC void nk_umeyama_f32_v128relaxed(nk_f32_t const *a, nk_f32_t const *b,
|
|
|
874
874
|
|
|
875
875
|
nk_f64_t trace_signed_singular_values = svd_diagonal[0] + svd_diagonal[4] +
|
|
876
876
|
(det < 0 ? -svd_diagonal[8] : svd_diagonal[8]);
|
|
877
|
-
computed_scale =
|
|
877
|
+
computed_scale = trace_signed_singular_values / centered_norm_squared_a;
|
|
878
878
|
|
|
879
879
|
trace_rotation_covariance =
|
|
880
880
|
optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
|
|
@@ -1059,7 +1059,7 @@ NK_PUBLIC void nk_umeyama_f64_v128relaxed(nk_f64_t const *a, nk_f64_t const *b,
|
|
|
1059
1059
|
optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
|
|
1060
1060
|
optimal_rotation[8] = 1;
|
|
1061
1061
|
trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
|
|
1062
|
-
computed_scale =
|
|
1062
|
+
computed_scale = trace_rotation_covariance / centered_norm_squared_a;
|
|
1063
1063
|
}
|
|
1064
1064
|
else {
|
|
1065
1065
|
nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
|
|
@@ -1069,7 +1069,7 @@ NK_PUBLIC void nk_umeyama_f64_v128relaxed(nk_f64_t const *a, nk_f64_t const *b,
|
|
|
1069
1069
|
// Handle reflection and compute scale
|
|
1070
1070
|
nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
|
|
1071
1071
|
nk_f64_t trace_d_s = svd_diagonal[0] + svd_diagonal[4] + (det < 0 ? -svd_diagonal[8] : svd_diagonal[8]);
|
|
1072
|
-
computed_scale =
|
|
1072
|
+
computed_scale = trace_d_s / centered_norm_squared_a;
|
|
1073
1073
|
|
|
1074
1074
|
if (det < 0) {
|
|
1075
1075
|
svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
|
|
@@ -319,8 +319,8 @@ void svd3x3_(scalar_type_ const *a, scalar_type_ *svd_u, scalar_type_ *svd_s, sc
|
|
|
319
319
|
|
|
320
320
|
/**
|
|
321
321
|
* @brief Root Mean Square Deviation between two 3D point clouds (no alignment)
|
|
322
|
-
* @param[in] a,b Point clouds [
|
|
323
|
-
* @param[in]
|
|
322
|
+
* @param[in] a,b Point clouds [n x 3] interleaved (x0,y0,z0, x1,y1,z1, ...)
|
|
323
|
+
* @param[in] n Number of 3D points
|
|
324
324
|
* @param[out] a_centroid,b_centroid Centroids (3 values each), can be nullptr
|
|
325
325
|
* @param[out] rotation 3x3 rotation matrix (9 values), always identity, can be nullptr
|
|
326
326
|
* @param[out] scale Scale factor, always 1.0, can be nullptr
|
|
@@ -6,7 +6,7 @@ These are used in variational inference, topic modeling, and distribution compar
|
|
|
6
6
|
Kullback-Leibler divergence from $P$ to $Q$:
|
|
7
7
|
|
|
8
8
|
$$
|
|
9
|
-
\text{KLD}(P \| Q) = \sum_{i=0}^{n-1} P(i) \
|
|
9
|
+
\text{KLD}(P \| Q) = \sum_{i=0}^{n-1} P(i) \ln \frac{P(i)}{Q(i)}
|
|
10
10
|
$$
|
|
11
11
|
|
|
12
12
|
Jensen-Shannon distance is the square root of the symmetrized KLD through a mixture:
|
|
@@ -28,9 +28,8 @@ Reformulating as Python pseudocode:
|
|
|
28
28
|
```python
|
|
29
29
|
import numpy as np
|
|
30
30
|
|
|
31
|
-
def kld(p: np.ndarray, q: np.ndarray) -> float:
|
|
32
|
-
|
|
33
|
-
return np.sum(p[mask] * np.log2(p[mask] / q[mask]))
|
|
31
|
+
def kld(p: np.ndarray, q: np.ndarray, eps: float = 1e-7) -> float:
|
|
32
|
+
return np.sum(p * np.log((p + eps) / (q + eps)))
|
|
34
33
|
|
|
35
34
|
def jsd(p: np.ndarray, q: np.ndarray) -> float:
|
|
36
35
|
m = (p + q) / 2
|
|
@@ -45,12 +44,12 @@ __Jensen-Shannon distance__ is commonly used in microbiome community comparison
|
|
|
45
44
|
|
|
46
45
|
## Input & Output Types
|
|
47
46
|
|
|
48
|
-
| Input Type | Output Type | Description
|
|
49
|
-
| ---------- | ----------- |
|
|
50
|
-
| `f64` | `f64` | 64-bit IEEE 754 double precision
|
|
51
|
-
| `f32` | `
|
|
52
|
-
| `f16` | `f32` | 16-bit IEEE 754 half precision, widened output
|
|
53
|
-
| `bf16` | `f32` | 16-bit brain float, widened output
|
|
47
|
+
| Input Type | Output Type | Description |
|
|
48
|
+
| ---------- | ----------- | ------------------------------------------------ |
|
|
49
|
+
| `f64` | `f64` | 64-bit IEEE 754 double precision |
|
|
50
|
+
| `f32` | `f64` | 32-bit IEEE 754 single precision, widened output |
|
|
51
|
+
| `f16` | `f32` | 16-bit IEEE 754 half precision, widened output |
|
|
52
|
+
| `bf16` | `f32` | 16-bit brain float, widened output |
|
|
54
53
|
|
|
55
54
|
## Optimizations
|
|
56
55
|
|
|
@@ -61,24 +60,27 @@ The pipeline on Skylake is:
|
|
|
61
60
|
|
|
62
61
|
```
|
|
63
62
|
exponent = VGETEXPPS(x)
|
|
64
|
-
mantissa = VGETMANTPS(x, normalize_to_[1,2))
|
|
65
|
-
|
|
63
|
+
mantissa = VGETMANTPS(x, normalize_to_[1,2))
|
|
64
|
+
s = (mantissa - 1) / (mantissa + 1)
|
|
65
|
+
log2(x) ≈ exponent + (2/ln2) · s · (1 + s²/3 + s⁴/5 + s⁶/7 + s⁸/9)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
`VGETEXP` extracts the unbiased exponent as a float, while `VGETMANT` normalizes the mantissa to $[1, 2)$.
|
|
69
|
-
A
|
|
69
|
+
A five-term odd series in $s$ — the $\operatorname{atanh}$ expansion, which converges quickly because $s \in [0, 1/3]$ — completes the approximation.
|
|
70
70
|
These instructions handle subnormals correctly without extra integer bit manipulation.
|
|
71
71
|
|
|
72
72
|
`nk_kld_f32_neon`, `nk_jsd_f32_neon`, `nk_kld_f16_haswell`, `nk_jsd_f16_haswell` use integer bit extraction instead:
|
|
73
73
|
|
|
74
74
|
```
|
|
75
75
|
exponent = (reinterpret_as_int(x) >> 23) - 127
|
|
76
|
-
mantissa = reinterpret_as_float((reinterpret_as_int(x) & 0x7FFFFF) | 0x3F800000)
|
|
77
|
-
log2(x) ≈ exponent +
|
|
76
|
+
mantissa = reinterpret_as_float((reinterpret_as_int(x) & 0x7FFFFF) | 0x3F800000)
|
|
77
|
+
log2(x) ≈ exponent + series_or_polynomial(mantissa)
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
This approach reinterprets the float as an integer, shifts out the mantissa bits to obtain the exponent, then masks and recombines to produce a normalized mantissa in $[1, 2)$.
|
|
81
|
+
The Haswell kernels then reuse the same $s$-series as Skylake, while the NEON kernels evaluate a degree-5 polynomial in the mantissa scaled by $m - 1$.
|
|
81
82
|
It works on any ISA with integer-float reinterpretation and avoids the need for specialized exponent/mantissa instructions.
|
|
83
|
+
The SIMD kernels scale the accumulated $\log_2$ sum by $\ln 2$ before returning, while the serial ones call a natural-log helper directly — either way the reported divergence is in nats, not bits.
|
|
82
84
|
|
|
83
85
|
### Kahan Compensated Summation for Float64
|
|
84
86
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
#include "numkong/types.h"
|
|
16
16
|
#include "numkong/spatial/haswell.h" // `nk_f32_sqrt_haswell`, `nk_f64_sqrt_haswell`
|
|
17
|
-
#include "numkong/spatial/skylake.h"
|
|
17
|
+
#include "numkong/spatial/skylake.h"
|
|
18
18
|
|
|
19
19
|
#if defined(__cplusplus)
|
|
20
20
|
extern "C" {
|
|
@@ -31,36 +31,39 @@ def minmax(a: np.ndarray) -> tuple[float, int, float, int]:
|
|
|
31
31
|
|
|
32
32
|
## Input & Output Types
|
|
33
33
|
|
|
34
|
+
The Output Type column lists the accumulator type of `nk_reduce_moments_*`.
|
|
35
|
+
`nk_reduce_minmax_*` instead reports extrema in the input type — `i4`, `u4`, and `u1` unpack to `i8`, `u8`, and `u8` — alongside two `nk_size_t` indices.
|
|
36
|
+
|
|
34
37
|
Float reductions:
|
|
35
38
|
|
|
36
|
-
| Input Type | Output Type | Description
|
|
37
|
-
| ---------- | ----------- |
|
|
38
|
-
| `f64` | `f64` | 64-bit double precision
|
|
39
|
-
| `f32` | `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
39
|
+
| Input Type | Output Type | Description |
|
|
40
|
+
| ---------- | ----------- | --------------------------------------- |
|
|
41
|
+
| `f64` | `f64` | 64-bit double precision |
|
|
42
|
+
| `f32` | `f64` | 32-bit single precision, widened output |
|
|
43
|
+
| `bf16` | `f32` | 16-bit brain float, widened output |
|
|
44
|
+
| `f16` | `f32` | 16-bit half precision, widened output |
|
|
42
45
|
|
|
43
46
|
Mini-float reductions:
|
|
44
47
|
|
|
45
48
|
| Input Type | Output Type | Description |
|
|
46
49
|
| ---------- | ----------- | -------------------------------------------- |
|
|
47
|
-
| `e4m3` | `f32` | 8-bit Float8: 4 exponent, 3 mantissa bits |
|
|
48
50
|
| `e5m2` | `f32` | 8-bit Float8: 5 exponent, 2 mantissa bits |
|
|
49
|
-
| `
|
|
51
|
+
| `e4m3` | `f32` | 8-bit Float8: 4 exponent, 3 mantissa bits |
|
|
50
52
|
| `e3m2` | `f32` | 8-bit MX format: 3 exponent, 2 mantissa bits |
|
|
53
|
+
| `e2m3` | `f32` | 8-bit MX format: 2 exponent, 3 mantissa bits |
|
|
51
54
|
|
|
52
55
|
Integer reductions:
|
|
53
56
|
|
|
54
57
|
| Input Type | Output Type | Description |
|
|
55
58
|
| ---------- | ----------- | ---------------------------------- |
|
|
56
|
-
| `i8` | `i64` | 8-bit signed, widened to 64-bit |
|
|
57
|
-
| `u8` | `u64` | 8-bit unsigned, widened to 64-bit |
|
|
58
|
-
| `i16` | `i64` | 16-bit signed, widened to 64-bit |
|
|
59
|
-
| `u16` | `u64` | 16-bit unsigned, widened to 64-bit |
|
|
60
|
-
| `i32` | `i64` | 32-bit signed, widened to 64-bit |
|
|
61
|
-
| `u32` | `u64` | 32-bit unsigned, widened to 64-bit |
|
|
62
59
|
| `i64` | `i64` | 64-bit signed |
|
|
60
|
+
| `i32` | `i64` | 32-bit signed, widened to 64-bit |
|
|
61
|
+
| `i16` | `i64` | 16-bit signed, widened to 64-bit |
|
|
62
|
+
| `i8` | `i64` | 8-bit signed, widened to 64-bit |
|
|
63
63
|
| `u64` | `u64` | 64-bit unsigned |
|
|
64
|
+
| `u32` | `u64` | 32-bit unsigned, widened to 64-bit |
|
|
65
|
+
| `u16` | `u64` | 16-bit unsigned, widened to 64-bit |
|
|
66
|
+
| `u8` | `u64` | 8-bit unsigned, widened to 64-bit |
|
|
64
67
|
|
|
65
68
|
Sub-byte reductions:
|
|
66
69
|
|
|
@@ -82,29 +85,32 @@ RVV uses native strided loads (`__riscv_vlse32_v_f32m1`) that accept arbitrary b
|
|
|
82
85
|
|
|
83
86
|
### Kahan-Neumaier Compensated Summation
|
|
84
87
|
|
|
85
|
-
`nk_reduce_moments_f32_serial`, `
|
|
88
|
+
`nk_reduce_moments_f32_serial`, `nk_reduce_moments_f64_serial` use Neumaier's variant of Kahan summation — maintaining a running compensation term that captures rounding errors.
|
|
86
89
|
Standard pairwise summation accumulates $O(\sqrt{n})$ ULP error for n elements; Neumaier compensation bounds error to $O(1)$ ULP regardless of vector length.
|
|
87
90
|
The serial path uses Neumaier's adaptive branch: `if (abs(sum) >= abs(val))` selects the larger summand first, minimizing relative error in the compensation term.
|
|
88
|
-
SIMD backends (`
|
|
91
|
+
SIMD backends (`nk_reduce_moments_f64_haswell`, `nk_reduce_moments_f64_skylake`) carry 4 or 8 independent compensation lanes in a YMM or ZMM register — computing `round_error = tentative - sum; correction = (sum - (tentative - round_error)) + (val - round_error)` without branches, folding all lanes into a single scalar correction at the end.
|
|
92
|
+
The Float32 SIMD kernels skip compensation: they widen every input to Float64 first, and the wider accumulator already absorbs the rounding error.
|
|
89
93
|
|
|
90
94
|
### Fused Moments in a Single Pass
|
|
91
95
|
|
|
92
|
-
`nk_reduce_moments_f32_haswell`, `nk_reduce_moments_f64_skylake` compute sum and sum-of-squares simultaneously — one load feeds both a
|
|
96
|
+
`nk_reduce_moments_f32_haswell`, `nk_reduce_moments_f64_skylake` compute sum and sum-of-squares simultaneously — one load feeds both a sum accumulator and a square accumulator.
|
|
97
|
+
On Haswell the Float32 inputs are widened with `VCVTPS2PD`, then folded in with `VADDPD` and `VFMADD231PD`; on Skylake the Float64 path squares with `VMULPD` so the product can go through the compensated add.
|
|
93
98
|
Two accumulators share the same loaded data, halving memory bandwidth compared to separate sum + norm passes.
|
|
94
99
|
The squared-norm $\|a\|^2 = \sum a_i^2$ is a self-dot-product, reused by packing routines (`nk_dots_pack_f32_haswell`) to precompute per-vector norms during layout transformation.
|
|
95
|
-
For Float16/BFloat16/Float8 inputs, all backends widen to Float32 before accumulation — NEON FHM (`nk_reduce_moments_e4m3_neonfhm`) converts e4m3->f16
|
|
100
|
+
For Float16/BFloat16/Float8 inputs, all backends widen to Float32 before accumulation — NEON FHM (`nk_reduce_moments_e4m3_neonfhm`) converts e4m3->f16 by bit manipulation, then uses `vfmlalq_low_f16` and `vfmlalq_high_f16` to fuse the Float16 → Float32 widening with the FMA into the Float32 accumulator.
|
|
96
101
|
|
|
97
102
|
### Integer Saturation in Sum-of-Squares
|
|
98
103
|
|
|
99
104
|
Integer moments accumulate sums in the widest available type: Int8/UInt8/Int16 inputs produce Int64/UInt64 outputs.
|
|
100
105
|
Sums use widening addition chains — NEON uses pairwise widening (`vpaddlq_s16` -> UInt32 -> UInt64 stages); Haswell biases Int8 inputs with 0x80 and uses unsigned SAD (`_mm256_sad_epu8`) for the sum, correcting by subtracting $128 \times \text{count}$ at the end.
|
|
101
|
-
Sum-of-squares can overflow UInt64 when squaring
|
|
106
|
+
Sum-of-squares can overflow UInt64 when squaring Int64/UInt64 inputs — only `nk_reduce_moments_i64_*` and `nk_reduce_moments_u64_*` need an explicit saturating multiply: it checks whether `abs(val) < 2^32` (so the square fits in UInt64) and otherwise saturates to I64_MAX for signed inputs, U64_MAX for unsigned ones.
|
|
107
|
+
Narrower inputs cannot overflow the product itself, so they only need the saturating accumulate.
|
|
102
108
|
Haswell emulates UInt64 saturating add via XOR-based unsigned comparison: flip sign bits to convert unsigned overflow detection into a signed comparison, then OR with the overflow mask to produce all-ones on saturation.
|
|
103
109
|
|
|
104
110
|
### Recursive Blocking for Counter Overflow
|
|
105
111
|
|
|
106
112
|
All SIMD backends use loop iteration counters narrower than `nk_size_t` to save register pressure — UInt8 for Int8 minmax lanes, UInt16 for Float32 moments lanes.
|
|
107
|
-
When `count` exceeds the counter's range x lane count (e.g., Haswell Float32: $
|
|
113
|
+
When `count` exceeds the counter's range x lane count (e.g., Haswell Float32 moments: $65536 \times 8 = 524288$ elements for UInt16 counters), the reduction splits recursively: process the left half, process the right half, combine results with saturating arithmetic.
|
|
108
114
|
Block caps vary by backend and element width: Haswell Int8 minmax uses UInt8 loop counters (cap = $256 \times 32 = 8192$); Skylake Float32 moments uses UInt16 counters (cap = $65536 \times 16 = 1048576$).
|
|
109
115
|
The recursive split is invisible to the caller — the public API accepts arbitrary `count` values; internal dispatch chooses between single-pass and recursive based on the cap.
|
|
110
116
|
|
|
@@ -118,7 +124,7 @@ NEON uses same-width counters (u8x16 for i8x16 minmax), limiting block size to $
|
|
|
118
124
|
### NaN-Aware Extrema Tracking
|
|
119
125
|
|
|
120
126
|
`nk_reduce_minmax_f32_haswell`, `nk_reduce_minmax_f64_skylake` use IEEE ordered-quiet comparisons (`_CMP_LT_OQ`, `_CMP_GT_OQ`) — returning false when either operand is NaN, so NaN inputs never replace the running extremum.
|
|
121
|
-
Tail elements beyond the vector-aligned portion are
|
|
127
|
+
Tail elements beyond the vector-aligned portion are blended into a quiet-NaN register — `nk_reduce_minmax_f64_haswell` partial-loads the tail and `_mm256_blendv_pd`-s it over a NaN vector, so the padding lanes cannot win any comparison; Skylake instead predicates the tail comparison with the same `__mmask` used for the load, and `nk_reduce_minmax_f32_haswell` finishes its tail in scalar code.
|
|
122
128
|
If all inputs are NaN, the sentinels remain (min = F32_MAX, max = F32_MIN) and indices are set to NK_SIZE_MAX, signaling no valid extremum.
|
|
123
129
|
The final horizontal reduction across lanes uses pairwise `VSHUFPS` + `VMINPS` chains — 3 shuffles for a 256-bit register, $O(\log_2 w)$ for width $w$.
|
|
124
130
|
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
#if NK_TARGET_GENOA
|
|
23
23
|
|
|
24
24
|
#include "numkong/reduce/serial.h"
|
|
25
|
-
#include "numkong/cast/icelake.h"
|
|
25
|
+
#include "numkong/cast/icelake.h" // `nk_e4m3x32_to_bf16x32_icelake_` etc.
|
|
26
|
+
#include "numkong/reduce/skylake.h" // `nk_reduce_add_f32x16_skylake_`
|
|
26
27
|
|
|
27
28
|
#if defined(__cplusplus)
|
|
28
29
|
extern "C" {
|
|
@@ -2218,7 +2218,7 @@ NK_INTERNAL void nk_reduce_moments_i64_neon_contiguous_( //
|
|
|
2218
2218
|
nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
|
|
2219
2219
|
uint64x2_t sum_low_u64x2 = vdupq_n_u64(0);
|
|
2220
2220
|
int64x2_t sum_high_i64x2 = vdupq_n_s64(0);
|
|
2221
|
-
// NEON can still load/extract i64 vectors for sumsq via
|
|
2221
|
+
// NEON can still load/extract i64 vectors for sumsq via nk_i64_smul_sq_i64x2_neon_
|
|
2222
2222
|
uint64x2_t sumsq_u64x2 = vdupq_n_u64(0);
|
|
2223
2223
|
int sumsq_overflow = 0;
|
|
2224
2224
|
int64x2_t sign_bit_i64x2 = vdupq_n_s64((nk_i64_t)0x8000000000000000ULL);
|
|
@@ -21,15 +21,6 @@
|
|
|
21
21
|
extern "C" {
|
|
22
22
|
#endif
|
|
23
23
|
|
|
24
|
-
/* Keep the serial instantiations below actually scalar, regardless of build type.
|
|
25
|
-
* See dots/serial.h for rationale. */
|
|
26
|
-
#if defined(__clang__)
|
|
27
|
-
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
28
|
-
#elif defined(__GNUC__)
|
|
29
|
-
#pragma GCC push_options
|
|
30
|
-
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
31
|
-
#endif
|
|
32
|
-
|
|
33
24
|
NK_INTERNAL nk_f64_t nk_reduce_sum_f64_serial_(nk_f64_t const *values, nk_f64_t const *compensations, int count) {
|
|
34
25
|
nk_f64_t running_sum = 0, accumulated_error = 0;
|
|
35
26
|
for (int i = 0; i < count; i++) {
|
|
@@ -47,6 +38,15 @@ NK_INTERNAL nk_f64_t nk_reduce_sum_f64_serial_(nk_f64_t const *values, nk_f64_t
|
|
|
47
38
|
return running_sum + accumulated_error;
|
|
48
39
|
}
|
|
49
40
|
|
|
41
|
+
/* Keep the serial instantiations below actually scalar, regardless of build type.
|
|
42
|
+
* See dots/serial.h for rationale. */
|
|
43
|
+
#if defined(__clang__)
|
|
44
|
+
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
45
|
+
#elif defined(__GNUC__)
|
|
46
|
+
#pragma GCC push_options
|
|
47
|
+
#pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
50
|
NK_PUBLIC void nk_reduce_moments_f32_serial( //
|
|
51
51
|
nk_f32_t const *data, nk_size_t count, nk_size_t stride_bytes, //
|
|
52
52
|
nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
|