usearch 2.26.1 → 2.26.3

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.
Files changed (146) hide show
  1. package/README.md +1 -1
  2. package/include/usearch/index.hpp +5 -1
  3. package/numkong/CITATION.cff +13 -9
  4. package/numkong/CONTRIBUTING.md +29 -24
  5. package/numkong/Cargo.toml +1 -1
  6. package/numkong/README.md +21 -7
  7. package/numkong/binding.gyp +47 -31
  8. package/numkong/build.rs +15 -5
  9. package/numkong/c/dispatch.h +0 -1
  10. package/numkong/c/parallel.c +139 -0
  11. package/numkong/c/parallel.h +47 -0
  12. package/numkong/cmake/toolchain-wasi.cmake +39 -5
  13. package/numkong/include/README.md +24 -17
  14. package/numkong/include/numkong/attention/sapphireamx.h +6 -6
  15. package/numkong/include/numkong/attention/sme.h +2 -2
  16. package/numkong/include/numkong/capabilities.h +2 -2
  17. package/numkong/include/numkong/cast/README.md +9 -7
  18. package/numkong/include/numkong/cast/loongsonasx.h +48 -40
  19. package/numkong/include/numkong/cast/serial.h +89 -11
  20. package/numkong/include/numkong/cast.h +3 -3
  21. package/numkong/include/numkong/curved/README.md +10 -10
  22. package/numkong/include/numkong/curved/neon.h +1 -0
  23. package/numkong/include/numkong/curved/neonbfdot.h +1 -0
  24. package/numkong/include/numkong/curved/smef64.h +1 -0
  25. package/numkong/include/numkong/dot/README.md +18 -18
  26. package/numkong/include/numkong/dot/alder.h +8 -8
  27. package/numkong/include/numkong/dot/haswell.h +8 -8
  28. package/numkong/include/numkong/dot/icelake.h +12 -12
  29. package/numkong/include/numkong/dot/loongsonasx.h +6 -6
  30. package/numkong/include/numkong/dot/neon.h +2 -1
  31. package/numkong/include/numkong/dot/neonbfdot.h +1 -0
  32. package/numkong/include/numkong/dot/neonfhm.h +1 -0
  33. package/numkong/include/numkong/dot/neonsdot.h +1 -0
  34. package/numkong/include/numkong/dot/powervsx.h +3 -3
  35. package/numkong/include/numkong/dot/serial.h +19 -6
  36. package/numkong/include/numkong/dot/skylake.h +4 -4
  37. package/numkong/include/numkong/dot/svehalf.h +1 -1
  38. package/numkong/include/numkong/dot/v128relaxed.h +24 -23
  39. package/numkong/include/numkong/dots/README.md +26 -26
  40. package/numkong/include/numkong/dots/genoa.h +3 -1
  41. package/numkong/include/numkong/dots/icelake.h +1 -0
  42. package/numkong/include/numkong/dots/powervsx.h +1 -1
  43. package/numkong/include/numkong/dots/serial.h +32 -33
  44. package/numkong/include/numkong/dots/sierra.h +4 -3
  45. package/numkong/include/numkong/each/README.md +5 -3
  46. package/numkong/include/numkong/each/sapphire.h +2 -0
  47. package/numkong/include/numkong/each/skylake.h +1 -0
  48. package/numkong/include/numkong/geospatial/README.md +6 -6
  49. package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
  50. package/numkong/include/numkong/maxsim/README.md +5 -5
  51. package/numkong/include/numkong/maxsim/genoa.h +4 -0
  52. package/numkong/include/numkong/maxsim/icelake.h +1 -0
  53. package/numkong/include/numkong/maxsim/serial.h +22 -9
  54. package/numkong/include/numkong/maxsim/sme.h +3 -2
  55. package/numkong/include/numkong/mesh/README.md +7 -4
  56. package/numkong/include/numkong/mesh/genoa.h +2 -2
  57. package/numkong/include/numkong/mesh/haswell.h +8 -8
  58. package/numkong/include/numkong/mesh/neon.h +6 -6
  59. package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
  60. package/numkong/include/numkong/mesh/neonfhm.h +3 -2
  61. package/numkong/include/numkong/mesh/rvv.h +4 -4
  62. package/numkong/include/numkong/mesh/serial.h +17 -17
  63. package/numkong/include/numkong/mesh/skylake.h +4 -4
  64. package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
  65. package/numkong/include/numkong/mesh.hpp +2 -2
  66. package/numkong/include/numkong/numkong.h +1 -1
  67. package/numkong/include/numkong/numkong.hpp +1 -1
  68. package/numkong/include/numkong/probability/README.md +17 -15
  69. package/numkong/include/numkong/probability/skylake.h +1 -1
  70. package/numkong/include/numkong/reduce/README.md +27 -21
  71. package/numkong/include/numkong/reduce/genoa.h +2 -1
  72. package/numkong/include/numkong/reduce/icelake.h +1 -0
  73. package/numkong/include/numkong/reduce/neon.h +1 -1
  74. package/numkong/include/numkong/reduce/serial.h +9 -9
  75. package/numkong/include/numkong/reduce/skylake.h +4 -4
  76. package/numkong/include/numkong/reduce.hpp +38 -0
  77. package/numkong/include/numkong/scalar/README.md +29 -28
  78. package/numkong/include/numkong/set/README.md +17 -14
  79. package/numkong/include/numkong/set/haswell.h +15 -16
  80. package/numkong/include/numkong/set/icelake.h +15 -16
  81. package/numkong/include/numkong/set/loongsonasx.h +12 -10
  82. package/numkong/include/numkong/set/neon.h +15 -19
  83. package/numkong/include/numkong/set/powervsx.h +15 -21
  84. package/numkong/include/numkong/set/serial.h +19 -20
  85. package/numkong/include/numkong/set/v128relaxed.h +10 -8
  86. package/numkong/include/numkong/sets/icelake.h +1 -0
  87. package/numkong/include/numkong/sparse/README.md +4 -4
  88. package/numkong/include/numkong/sparse/icelake.h +1 -0
  89. package/numkong/include/numkong/sparse/neon.h +1 -0
  90. package/numkong/include/numkong/sparse/serial.h +10 -3
  91. package/numkong/include/numkong/sparse/turin.h +1 -0
  92. package/numkong/include/numkong/spatial/README.md +25 -24
  93. package/numkong/include/numkong/spatial/alder.h +2 -1
  94. package/numkong/include/numkong/spatial/haswell.h +48 -40
  95. package/numkong/include/numkong/spatial/icelake.h +2 -1
  96. package/numkong/include/numkong/spatial/neon.h +54 -46
  97. package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
  98. package/numkong/include/numkong/spatial/powervsx.h +54 -46
  99. package/numkong/include/numkong/spatial/serial.h +56 -46
  100. package/numkong/include/numkong/spatial/sierra.h +4 -3
  101. package/numkong/include/numkong/spatial/skylake.h +27 -22
  102. package/numkong/include/numkong/spatial/sve.h +6 -6
  103. package/numkong/include/numkong/spatial/svebfdot.h +1 -1
  104. package/numkong/include/numkong/spatial/svehalf.h +1 -1
  105. package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
  106. package/numkong/include/numkong/spatials/README.md +15 -15
  107. package/numkong/include/numkong/spatials/genoa.h +3 -0
  108. package/numkong/include/numkong/spatials/graniteamx.h +2 -0
  109. package/numkong/include/numkong/spatials/icelake.h +1 -0
  110. package/numkong/include/numkong/spatials/sierra.h +1 -0
  111. package/numkong/include/numkong/tensor.hpp +236 -123
  112. package/numkong/include/numkong/trigonometry/README.md +4 -4
  113. package/numkong/include/numkong/trigonometry/serial.h +6 -6
  114. package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
  115. package/numkong/include/numkong/types.hpp +46 -16
  116. package/numkong/include/numkong/vector.hpp +116 -23
  117. package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
  118. package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
  119. package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
  120. package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
  121. package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
  122. package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
  123. package/numkong/javascript/README.md +18 -16
  124. package/numkong/javascript/numkong.c +70 -40
  125. package/numkong/package.json +8 -8
  126. package/numkong/rust/README.md +19 -9
  127. package/numkong/rust/cast.rs +52 -1
  128. package/numkong/rust/each.rs +480 -7
  129. package/numkong/rust/matrix.rs +877 -216
  130. package/numkong/rust/numkong.rs +12 -8
  131. package/numkong/rust/reduce.rs +433 -5
  132. package/numkong/rust/tensor.rs +450 -842
  133. package/numkong/rust/vector.rs +520 -101
  134. package/numkong/swift/README.md +5 -3
  135. package/numkong/test/README.md +4 -4
  136. package/numkong/test/test-wasi.mjs +17 -0
  137. package/numkong/test/test_each.cpp +30 -1
  138. package/numkong/test/test_each.py +74 -1
  139. package/numkong/test/test_reduce.cpp +51 -0
  140. package/numkong/test/test_tensor.cpp +2 -63
  141. package/numkong/test/test_tensor.py +93 -0
  142. package/package.json +1 -1
  143. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  144. package/prebuilds/linux-arm64/usearch.node +0 -0
  145. package/prebuilds/linux-x64/usearch.node +0 -0
  146. 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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_d_s / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_scaled_s / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_scaled_s / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_scaled_s / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
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(NDEBUG)
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(__clang__)
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_signed_singular_values / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
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 = centered_norm_squared_a > 0.0 ? trace_d_s / centered_norm_squared_a : 0.0;
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 [d x 3] interleaved (x0,y0,z0, x1,y1,z1, ...)
323
- * @param[in] d Number of 3D points
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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @brief SIMD-accelerated Similarity Measures and Distance Functions.
3
- * @file include/numkong.h
3
+ * @file include/numkong/numkong.h
4
4
  * @author Ash Vardanian
5
5
  * @date March 14, 2023
6
6
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @brief NumKong SDK for C++23 and newer.
3
- * @file include/numkong.hpp
3
+ * @file include/numkong/numkong.hpp
4
4
  * @author Ash Vardanian
5
5
  * @date January 7, 2026
6
6
  *
@@ -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) \log_2 \frac{P(i)}{Q(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
- mask = p > 0
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` | `f32` | 32-bit IEEE 754 single precision |
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)) - 1
65
- log2(x) ≈ exponent + polynomial(mantissa)
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 degree-4 minimax polynomial over the normalized mantissa completes the approximation.
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) - 1
77
- log2(x) ≈ exponent + c₁·m + c₂·m² + c₃·m³ + c₄·m⁴ + c₅·m⁵
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" // `nk_f32_sqrt_skylake`, `nk_f64_sqrt_skylake`
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` | `f32` | 32-bit single precision |
40
- | `f16` | `f32` | 16-bit half precision, widened output |
41
- | `bf16` | `f32` | 16-bit brain float, widened output |
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
- | `e2m3` | `f32` | 8-bit MX format: 2 exponent, 3 mantissa bits |
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`, `nk_reduce_moments_f32_haswell` use Neumaier's variant of Kahan summation — maintaining a running compensation term that captures rounding errors.
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 (`nk_reduce_moments_f32_haswell`) carry 8 independent compensation lanes in a YMM 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.
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 `VADDPS` (sum accumulator) and a `VFMADD231PS` (square accumulator).
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 via lookup, then uses `vfmlalq_low_f16` to fuse the Float16 → Float32 widening with the FMA into the Float32 accumulator.
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 large Int32 values — backends use explicit saturating multiply: checks if `abs(val) < 2^32` (square fits in UInt64), otherwise saturates to I64_MAX.
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: $256 \times 8 = 2048$ elements for UInt8 counters), the reduction splits recursively: process the left half, process the right half, combine results with saturating arithmetic.
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 masked by loading into a NaN-filled register via `_mm256_mask_loadu_ps(nan_vec, mask, ptr)` — NaN tails cannot win any comparison, eliminating out-of-bounds artifacts.
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" // `nk_e4m3x32_to_bf16x32_icelake_` etc.
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" {
@@ -19,6 +19,7 @@
19
19
  #if NK_TARGET_ICELAKE
20
20
 
21
21
  #include "numkong/reduce/serial.h"
22
+ #include "numkong/reduce/skylake.h" // `nk_reduce_add_i64x8_skylake_`, `nk_reduce_add_u64x8_skylake_`, `nk_stride_mask_b16x32_`
22
23
 
23
24
  #if defined(__cplusplus)
24
25
  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 scalar nk_i64_smul_
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) {