usearch 2.21.4 → 2.25.0

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 (602) hide show
  1. package/README.md +24 -24
  2. package/binding.gyp +15 -37
  3. package/c/CMakeLists.txt +4 -5
  4. package/c/README.md +7 -7
  5. package/c/lib.cpp +32 -14
  6. package/c/test.c +83 -1
  7. package/c/usearch.h +24 -3
  8. package/cpp/CMakeLists.txt +4 -0
  9. package/cpp/README.md +5 -5
  10. package/cpp/bench.cpp +97 -116
  11. package/cpp/test.cpp +117 -20
  12. package/include/usearch/index.hpp +224 -48
  13. package/include/usearch/index_dense.hpp +105 -40
  14. package/include/usearch/index_plugins.hpp +1387 -204
  15. package/javascript/dist/cjs/usearch.d.ts +8 -3
  16. package/javascript/dist/cjs/usearch.js +12 -4
  17. package/javascript/dist/esm/usearch.d.ts +8 -3
  18. package/javascript/dist/esm/usearch.js +12 -4
  19. package/javascript/lib.cpp +17 -0
  20. package/javascript/usearch.ts +14 -6
  21. package/numkong/.clang-tidy +30 -0
  22. package/{simsimd → numkong}/.cmake-format.py +18 -0
  23. package/numkong/.editorconfig +12 -0
  24. package/numkong/.git-blame-ignore-revs +123 -0
  25. package/numkong/.mailmap +15 -0
  26. package/numkong/.spi.yml +11 -0
  27. package/numkong/.swift-format +14 -0
  28. package/numkong/CITATION.cff +81 -0
  29. package/numkong/CONTRIBUTING.md +500 -0
  30. package/numkong/Cargo.toml +54 -0
  31. package/numkong/MANIFEST.in +8 -0
  32. package/numkong/Package.swift +84 -0
  33. package/numkong/README.md +569 -0
  34. package/numkong/bench/README.md +217 -0
  35. package/numkong/bench/bench-browser.html +323 -0
  36. package/numkong/bench/bench.cpp +344 -0
  37. package/numkong/bench/bench.hpp +816 -0
  38. package/numkong/bench/bench.js +148 -0
  39. package/numkong/bench/bench.mjs +616 -0
  40. package/numkong/bench/bench_attention.cpp +113 -0
  41. package/numkong/bench/bench_cast.cpp +264 -0
  42. package/numkong/bench/bench_cross_amx.cpp +62 -0
  43. package/numkong/bench/bench_cross_arm.cpp +245 -0
  44. package/numkong/bench/bench_cross_blas.cpp +309 -0
  45. package/numkong/bench/bench_cross_loongarch.cpp +106 -0
  46. package/numkong/bench/bench_cross_power.cpp +84 -0
  47. package/numkong/bench/bench_cross_rvv.cpp +121 -0
  48. package/numkong/bench/bench_cross_serial.cpp +151 -0
  49. package/numkong/bench/bench_cross_sme.cpp +163 -0
  50. package/numkong/bench/bench_cross_wasm.cpp +142 -0
  51. package/numkong/bench/bench_cross_x86.cpp +455 -0
  52. package/numkong/bench/bench_curved.cpp +210 -0
  53. package/numkong/bench/bench_dot.cpp +315 -0
  54. package/numkong/bench/bench_each.cpp +503 -0
  55. package/numkong/bench/bench_geospatial.cpp +112 -0
  56. package/numkong/bench/bench_maxsim.cpp +154 -0
  57. package/numkong/bench/bench_mesh.cpp +148 -0
  58. package/numkong/bench/bench_probability.cpp +60 -0
  59. package/numkong/bench/bench_reduce.cpp +371 -0
  60. package/numkong/bench/bench_set.cpp +73 -0
  61. package/numkong/bench/bench_sparse.cpp +198 -0
  62. package/numkong/bench/bench_spatial.cpp +347 -0
  63. package/numkong/bench/bench_trigonometry.cpp +137 -0
  64. package/numkong/bench/swift/Bench.swift +506 -0
  65. package/numkong/binding.gyp +169 -0
  66. package/numkong/build.rs +484 -0
  67. package/numkong/c/dispatch.h +512 -0
  68. package/numkong/c/dispatch_bf16.c +447 -0
  69. package/numkong/c/dispatch_bf16c.c +52 -0
  70. package/numkong/c/dispatch_e2m3.c +296 -0
  71. package/numkong/c/dispatch_e3m2.c +284 -0
  72. package/numkong/c/dispatch_e4m3.c +318 -0
  73. package/numkong/c/dispatch_e5m2.c +316 -0
  74. package/numkong/c/dispatch_f16.c +452 -0
  75. package/numkong/c/dispatch_f16c.c +58 -0
  76. package/numkong/c/dispatch_f32.c +434 -0
  77. package/numkong/c/dispatch_f32c.c +99 -0
  78. package/numkong/c/dispatch_f64.c +348 -0
  79. package/numkong/c/dispatch_f64c.c +98 -0
  80. package/numkong/c/dispatch_i16.c +96 -0
  81. package/numkong/c/dispatch_i32.c +89 -0
  82. package/numkong/c/dispatch_i4.c +153 -0
  83. package/numkong/c/dispatch_i64.c +86 -0
  84. package/numkong/c/dispatch_i8.c +348 -0
  85. package/numkong/c/dispatch_other.c +348 -0
  86. package/numkong/c/dispatch_u1.c +193 -0
  87. package/numkong/c/dispatch_u16.c +124 -0
  88. package/numkong/c/dispatch_u32.c +118 -0
  89. package/numkong/c/dispatch_u4.c +153 -0
  90. package/numkong/c/dispatch_u64.c +102 -0
  91. package/numkong/c/dispatch_u8.c +364 -0
  92. package/numkong/c/numkong.c +940 -0
  93. package/numkong/cmake/nk_arm_isa_probes.cmake +61 -0
  94. package/numkong/cmake/nk_isa_probe.cmake +90 -0
  95. package/numkong/cmake/nk_loongarch_isa_probes.cmake +15 -0
  96. package/numkong/cmake/nk_math_lib.cmake +46 -0
  97. package/numkong/cmake/nk_power_isa_probes.cmake +15 -0
  98. package/numkong/cmake/nk_riscv_isa_probes.cmake +19 -0
  99. package/numkong/cmake/nk_x86_isa_probes.cmake +35 -0
  100. package/numkong/cmake/toolchain-aarch64-gnu.cmake +86 -0
  101. package/numkong/cmake/toolchain-android-arm64.cmake +25 -0
  102. package/numkong/cmake/toolchain-android-armv7.cmake +25 -0
  103. package/numkong/cmake/toolchain-loongarch64-gnu.cmake +68 -0
  104. package/numkong/cmake/toolchain-ppc64le-gnu.cmake +74 -0
  105. package/numkong/cmake/toolchain-riscv64-gnu.cmake +107 -0
  106. package/numkong/cmake/toolchain-riscv64-llvm.cmake +114 -0
  107. package/numkong/cmake/toolchain-wasi.cmake +77 -0
  108. package/numkong/cmake/toolchain-wasm.cmake +70 -0
  109. package/numkong/cmake/toolchain-wasm64.cmake +62 -0
  110. package/numkong/cmake/toolchain-x86_64-llvm.cmake +47 -0
  111. package/numkong/include/README.md +643 -0
  112. package/numkong/include/module.modulemap +129 -0
  113. package/numkong/include/numkong/attention/sapphireamx.h +1363 -0
  114. package/numkong/include/numkong/attention/sme.h +2075 -0
  115. package/numkong/include/numkong/attention.h +49 -0
  116. package/numkong/include/numkong/capabilities.h +930 -0
  117. package/numkong/include/numkong/cast/README.md +262 -0
  118. package/numkong/include/numkong/cast/diamond.h +64 -0
  119. package/numkong/include/numkong/cast/haswell.h +823 -0
  120. package/numkong/include/numkong/cast/icelake.h +475 -0
  121. package/numkong/include/numkong/cast/loongsonasx.h +252 -0
  122. package/numkong/include/numkong/cast/neon.h +1174 -0
  123. package/numkong/include/numkong/cast/powervsx.h +449 -0
  124. package/numkong/include/numkong/cast/rvv.h +970 -0
  125. package/numkong/include/numkong/cast/sapphire.h +262 -0
  126. package/numkong/include/numkong/cast/serial.h +2336 -0
  127. package/numkong/include/numkong/cast/skylake.h +915 -0
  128. package/numkong/include/numkong/cast/v128relaxed.h +537 -0
  129. package/numkong/include/numkong/cast.h +254 -0
  130. package/numkong/include/numkong/cast.hpp +44 -0
  131. package/numkong/include/numkong/curved/README.md +223 -0
  132. package/numkong/include/numkong/curved/genoa.h +182 -0
  133. package/numkong/include/numkong/curved/haswell.h +276 -0
  134. package/numkong/include/numkong/curved/neon.h +329 -0
  135. package/numkong/include/numkong/curved/neonbfdot.h +211 -0
  136. package/numkong/include/numkong/curved/rvv.h +305 -0
  137. package/numkong/include/numkong/curved/serial.h +207 -0
  138. package/numkong/include/numkong/curved/skylake.h +457 -0
  139. package/numkong/include/numkong/curved/smef64.h +530 -0
  140. package/numkong/include/numkong/curved.h +513 -0
  141. package/numkong/include/numkong/curved.hpp +144 -0
  142. package/numkong/include/numkong/dot/README.md +442 -0
  143. package/numkong/include/numkong/dot/alder.h +563 -0
  144. package/numkong/include/numkong/dot/diamond.h +158 -0
  145. package/numkong/include/numkong/dot/genoa.h +289 -0
  146. package/numkong/include/numkong/dot/haswell.h +1723 -0
  147. package/numkong/include/numkong/dot/icelake.h +997 -0
  148. package/numkong/include/numkong/dot/loongsonasx.h +671 -0
  149. package/numkong/include/numkong/dot/neon.h +869 -0
  150. package/numkong/include/numkong/dot/neonbfdot.h +243 -0
  151. package/numkong/include/numkong/dot/neonfhm.h +358 -0
  152. package/numkong/include/numkong/dot/neonfp8.h +323 -0
  153. package/numkong/include/numkong/dot/neonsdot.h +622 -0
  154. package/numkong/include/numkong/dot/powervsx.h +752 -0
  155. package/numkong/include/numkong/dot/rvv.h +722 -0
  156. package/numkong/include/numkong/dot/rvvbb.h +72 -0
  157. package/numkong/include/numkong/dot/rvvbf16.h +123 -0
  158. package/numkong/include/numkong/dot/rvvhalf.h +129 -0
  159. package/numkong/include/numkong/dot/sapphire.h +141 -0
  160. package/numkong/include/numkong/dot/serial.h +874 -0
  161. package/numkong/include/numkong/dot/sierra.h +405 -0
  162. package/numkong/include/numkong/dot/skylake.h +1116 -0
  163. package/numkong/include/numkong/dot/sve.h +420 -0
  164. package/numkong/include/numkong/dot/svebfdot.h +75 -0
  165. package/numkong/include/numkong/dot/svehalf.h +168 -0
  166. package/numkong/include/numkong/dot/svesdot.h +90 -0
  167. package/numkong/include/numkong/dot/v128relaxed.h +1293 -0
  168. package/numkong/include/numkong/dot.h +1136 -0
  169. package/numkong/include/numkong/dot.hpp +94 -0
  170. package/numkong/include/numkong/dots/README.md +496 -0
  171. package/numkong/include/numkong/dots/alder.h +117 -0
  172. package/numkong/include/numkong/dots/diamond.h +86 -0
  173. package/numkong/include/numkong/dots/genoa.h +100 -0
  174. package/numkong/include/numkong/dots/graniteamx.h +733 -0
  175. package/numkong/include/numkong/dots/haswell.h +310 -0
  176. package/numkong/include/numkong/dots/icelake.h +180 -0
  177. package/numkong/include/numkong/dots/loongsonasx.h +176 -0
  178. package/numkong/include/numkong/dots/neon.h +123 -0
  179. package/numkong/include/numkong/dots/neonbfdot.h +59 -0
  180. package/numkong/include/numkong/dots/neonfhm.h +96 -0
  181. package/numkong/include/numkong/dots/neonfp8.h +99 -0
  182. package/numkong/include/numkong/dots/neonsdot.h +144 -0
  183. package/numkong/include/numkong/dots/powervsx.h +194 -0
  184. package/numkong/include/numkong/dots/rvv.h +2593 -0
  185. package/numkong/include/numkong/dots/sapphireamx.h +4017 -0
  186. package/numkong/include/numkong/dots/serial.h +2867 -0
  187. package/numkong/include/numkong/dots/sierra.h +100 -0
  188. package/numkong/include/numkong/dots/skylake.h +207 -0
  189. package/numkong/include/numkong/dots/sme.h +5041 -0
  190. package/numkong/include/numkong/dots/smebi32.h +477 -0
  191. package/numkong/include/numkong/dots/smef64.h +1328 -0
  192. package/numkong/include/numkong/dots/smehalf.h +47 -0
  193. package/numkong/include/numkong/dots/v128relaxed.h +317 -0
  194. package/numkong/include/numkong/dots.h +3110 -0
  195. package/numkong/include/numkong/dots.hpp +641 -0
  196. package/numkong/include/numkong/each/README.md +465 -0
  197. package/numkong/include/numkong/each/haswell.h +1658 -0
  198. package/numkong/include/numkong/each/icelake.h +272 -0
  199. package/numkong/include/numkong/each/neon.h +1138 -0
  200. package/numkong/include/numkong/each/neonbfdot.h +211 -0
  201. package/numkong/include/numkong/each/neonhalf.h +318 -0
  202. package/numkong/include/numkong/each/rvv.h +1121 -0
  203. package/numkong/include/numkong/each/sapphire.h +343 -0
  204. package/numkong/include/numkong/each/serial.h +282 -0
  205. package/numkong/include/numkong/each/skylake.h +1562 -0
  206. package/numkong/include/numkong/each/v128relaxed.h +562 -0
  207. package/numkong/include/numkong/each.h +2232 -0
  208. package/numkong/include/numkong/each.hpp +434 -0
  209. package/numkong/include/numkong/geospatial/README.md +147 -0
  210. package/numkong/include/numkong/geospatial/haswell.h +633 -0
  211. package/numkong/include/numkong/geospatial/neon.h +615 -0
  212. package/numkong/include/numkong/geospatial/rvv.h +701 -0
  213. package/numkong/include/numkong/geospatial/serial.h +309 -0
  214. package/numkong/include/numkong/geospatial/skylake.h +613 -0
  215. package/numkong/include/numkong/geospatial/v128relaxed.h +652 -0
  216. package/numkong/include/numkong/geospatial.h +453 -0
  217. package/numkong/include/numkong/geospatial.hpp +235 -0
  218. package/numkong/include/numkong/matrix.hpp +336 -0
  219. package/numkong/include/numkong/maxsim/README.md +187 -0
  220. package/numkong/include/numkong/maxsim/alder.h +511 -0
  221. package/numkong/include/numkong/maxsim/genoa.h +115 -0
  222. package/numkong/include/numkong/maxsim/haswell.h +553 -0
  223. package/numkong/include/numkong/maxsim/icelake.h +480 -0
  224. package/numkong/include/numkong/maxsim/neonsdot.h +394 -0
  225. package/numkong/include/numkong/maxsim/sapphireamx.h +877 -0
  226. package/numkong/include/numkong/maxsim/serial.h +490 -0
  227. package/numkong/include/numkong/maxsim/sme.h +993 -0
  228. package/numkong/include/numkong/maxsim/v128relaxed.h +280 -0
  229. package/numkong/include/numkong/maxsim.h +571 -0
  230. package/numkong/include/numkong/maxsim.hpp +133 -0
  231. package/numkong/include/numkong/mesh/README.md +219 -0
  232. package/numkong/include/numkong/mesh/haswell.h +2013 -0
  233. package/numkong/include/numkong/mesh/neon.h +1821 -0
  234. package/numkong/include/numkong/mesh/neonbfdot.h +771 -0
  235. package/numkong/include/numkong/mesh/rvv.h +936 -0
  236. package/numkong/include/numkong/mesh/serial.h +721 -0
  237. package/numkong/include/numkong/mesh/skylake.h +2344 -0
  238. package/numkong/include/numkong/mesh/v128relaxed.h +1081 -0
  239. package/numkong/include/numkong/mesh.h +666 -0
  240. package/numkong/include/numkong/mesh.hpp +701 -0
  241. package/numkong/include/numkong/numkong.h +78 -0
  242. package/numkong/include/numkong/numkong.hpp +58 -0
  243. package/numkong/include/numkong/probability/README.md +177 -0
  244. package/numkong/include/numkong/probability/haswell.h +267 -0
  245. package/numkong/include/numkong/probability/neon.h +255 -0
  246. package/numkong/include/numkong/probability/rvv.h +414 -0
  247. package/numkong/include/numkong/probability/serial.h +181 -0
  248. package/numkong/include/numkong/probability/skylake.h +324 -0
  249. package/numkong/include/numkong/probability.h +380 -0
  250. package/numkong/include/numkong/probability.hpp +120 -0
  251. package/numkong/include/numkong/random.h +50 -0
  252. package/numkong/include/numkong/random.hpp +285 -0
  253. package/numkong/include/numkong/reduce/README.md +552 -0
  254. package/numkong/include/numkong/reduce/alder.h +636 -0
  255. package/numkong/include/numkong/reduce/genoa.h +201 -0
  256. package/numkong/include/numkong/reduce/haswell.h +3785 -0
  257. package/numkong/include/numkong/reduce/icelake.h +549 -0
  258. package/numkong/include/numkong/reduce/neon.h +3980 -0
  259. package/numkong/include/numkong/reduce/neonbfdot.h +142 -0
  260. package/numkong/include/numkong/reduce/neonfhm.h +293 -0
  261. package/numkong/include/numkong/reduce/neonsdot.h +357 -0
  262. package/numkong/include/numkong/reduce/rvv.h +3533 -0
  263. package/numkong/include/numkong/reduce/serial.h +753 -0
  264. package/numkong/include/numkong/reduce/sierra.h +338 -0
  265. package/numkong/include/numkong/reduce/skylake.h +3794 -0
  266. package/numkong/include/numkong/reduce/sve.h +52 -0
  267. package/numkong/include/numkong/reduce/v128relaxed.h +2302 -0
  268. package/numkong/include/numkong/reduce.h +1582 -0
  269. package/numkong/include/numkong/reduce.hpp +778 -0
  270. package/numkong/include/numkong/scalar/README.md +89 -0
  271. package/numkong/include/numkong/scalar/haswell.h +122 -0
  272. package/numkong/include/numkong/scalar/loongsonasx.h +74 -0
  273. package/numkong/include/numkong/scalar/neon.h +122 -0
  274. package/numkong/include/numkong/scalar/neonhalf.h +70 -0
  275. package/numkong/include/numkong/scalar/powervsx.h +96 -0
  276. package/numkong/include/numkong/scalar/rvv.h +211 -0
  277. package/numkong/include/numkong/scalar/sapphire.h +74 -0
  278. package/numkong/include/numkong/scalar/serial.h +332 -0
  279. package/numkong/include/numkong/scalar/v128relaxed.h +56 -0
  280. package/numkong/include/numkong/scalar.h +696 -0
  281. package/numkong/include/numkong/set/README.md +179 -0
  282. package/numkong/include/numkong/set/haswell.h +334 -0
  283. package/numkong/include/numkong/set/icelake.h +485 -0
  284. package/numkong/include/numkong/set/loongsonasx.h +181 -0
  285. package/numkong/include/numkong/set/neon.h +363 -0
  286. package/numkong/include/numkong/set/powervsx.h +326 -0
  287. package/numkong/include/numkong/set/rvv.h +226 -0
  288. package/numkong/include/numkong/set/rvvbb.h +117 -0
  289. package/numkong/include/numkong/set/serial.h +174 -0
  290. package/numkong/include/numkong/set/sve.h +187 -0
  291. package/numkong/include/numkong/set/v128relaxed.h +240 -0
  292. package/numkong/include/numkong/set.h +471 -0
  293. package/numkong/include/numkong/set.hpp +114 -0
  294. package/numkong/include/numkong/sets/README.md +149 -0
  295. package/numkong/include/numkong/sets/haswell.h +63 -0
  296. package/numkong/include/numkong/sets/icelake.h +66 -0
  297. package/numkong/include/numkong/sets/loongsonasx.h +52 -0
  298. package/numkong/include/numkong/sets/neon.h +61 -0
  299. package/numkong/include/numkong/sets/powervsx.h +65 -0
  300. package/numkong/include/numkong/sets/serial.h +43 -0
  301. package/numkong/include/numkong/sets/smebi32.h +1135 -0
  302. package/numkong/include/numkong/sets/v128relaxed.h +58 -0
  303. package/numkong/include/numkong/sets.h +382 -0
  304. package/numkong/include/numkong/sparse/README.md +156 -0
  305. package/numkong/include/numkong/sparse/icelake.h +463 -0
  306. package/numkong/include/numkong/sparse/neon.h +288 -0
  307. package/numkong/include/numkong/sparse/serial.h +117 -0
  308. package/numkong/include/numkong/sparse/sve2.h +504 -0
  309. package/numkong/include/numkong/sparse/turin.h +322 -0
  310. package/numkong/include/numkong/sparse.h +363 -0
  311. package/numkong/include/numkong/sparse.hpp +113 -0
  312. package/numkong/include/numkong/spatial/README.md +439 -0
  313. package/numkong/include/numkong/spatial/alder.h +604 -0
  314. package/numkong/include/numkong/spatial/diamond.h +240 -0
  315. package/numkong/include/numkong/spatial/genoa.h +222 -0
  316. package/numkong/include/numkong/spatial/haswell.h +979 -0
  317. package/numkong/include/numkong/spatial/icelake.h +1067 -0
  318. package/numkong/include/numkong/spatial/loongsonasx.h +483 -0
  319. package/numkong/include/numkong/spatial/neon.h +846 -0
  320. package/numkong/include/numkong/spatial/neonbfdot.h +164 -0
  321. package/numkong/include/numkong/spatial/neonfp8.h +258 -0
  322. package/numkong/include/numkong/spatial/neonsdot.h +429 -0
  323. package/numkong/include/numkong/spatial/powervsx.h +738 -0
  324. package/numkong/include/numkong/spatial/rvv.h +991 -0
  325. package/numkong/include/numkong/spatial/rvvbf16.h +128 -0
  326. package/numkong/include/numkong/spatial/rvvhalf.h +120 -0
  327. package/numkong/include/numkong/spatial/serial.h +347 -0
  328. package/numkong/include/numkong/spatial/sierra.h +516 -0
  329. package/numkong/include/numkong/spatial/skylake.h +605 -0
  330. package/numkong/include/numkong/spatial/sve.h +246 -0
  331. package/numkong/include/numkong/spatial/svebfdot.h +125 -0
  332. package/numkong/include/numkong/spatial/svehalf.h +136 -0
  333. package/numkong/include/numkong/spatial/svesdot.h +143 -0
  334. package/numkong/include/numkong/spatial/v128relaxed.h +969 -0
  335. package/numkong/include/numkong/spatial.h +1681 -0
  336. package/numkong/include/numkong/spatial.hpp +183 -0
  337. package/numkong/include/numkong/spatials/README.md +580 -0
  338. package/numkong/include/numkong/spatials/alder.h +94 -0
  339. package/numkong/include/numkong/spatials/diamond.h +82 -0
  340. package/numkong/include/numkong/spatials/genoa.h +94 -0
  341. package/numkong/include/numkong/spatials/graniteamx.h +173 -0
  342. package/numkong/include/numkong/spatials/haswell.h +219 -0
  343. package/numkong/include/numkong/spatials/icelake.h +113 -0
  344. package/numkong/include/numkong/spatials/loongsonasx.h +153 -0
  345. package/numkong/include/numkong/spatials/neon.h +109 -0
  346. package/numkong/include/numkong/spatials/neonbfdot.h +60 -0
  347. package/numkong/include/numkong/spatials/neonfhm.h +92 -0
  348. package/numkong/include/numkong/spatials/neonfp8.h +111 -0
  349. package/numkong/include/numkong/spatials/neonsdot.h +143 -0
  350. package/numkong/include/numkong/spatials/powervsx.h +153 -0
  351. package/numkong/include/numkong/spatials/rvv.h +1976 -0
  352. package/numkong/include/numkong/spatials/sapphireamx.h +1149 -0
  353. package/numkong/include/numkong/spatials/serial.h +248 -0
  354. package/numkong/include/numkong/spatials/sierra.h +96 -0
  355. package/numkong/include/numkong/spatials/skylake.h +184 -0
  356. package/numkong/include/numkong/spatials/sme.h +1927 -0
  357. package/numkong/include/numkong/spatials/smef64.h +483 -0
  358. package/numkong/include/numkong/spatials/v128relaxed.h +240 -0
  359. package/numkong/include/numkong/spatials.h +3374 -0
  360. package/numkong/include/numkong/spatials.hpp +509 -0
  361. package/numkong/include/numkong/tensor.hpp +1981 -0
  362. package/numkong/include/numkong/trigonometry/README.md +184 -0
  363. package/numkong/include/numkong/trigonometry/haswell.h +653 -0
  364. package/numkong/include/numkong/trigonometry/neon.h +638 -0
  365. package/numkong/include/numkong/trigonometry/rvv.h +700 -0
  366. package/numkong/include/numkong/trigonometry/serial.h +703 -0
  367. package/numkong/include/numkong/trigonometry/skylake.h +725 -0
  368. package/numkong/include/numkong/trigonometry/v128relaxed.h +667 -0
  369. package/numkong/include/numkong/trigonometry.h +467 -0
  370. package/numkong/include/numkong/trigonometry.hpp +166 -0
  371. package/numkong/include/numkong/types.h +1695 -0
  372. package/numkong/include/numkong/types.hpp +6027 -0
  373. package/numkong/include/numkong/vector.hpp +766 -0
  374. package/numkong/javascript/@numkong-darwin-arm64/package.json +26 -0
  375. package/numkong/javascript/@numkong-darwin-x64/package.json +26 -0
  376. package/numkong/javascript/@numkong-linux-arm64/package.json +25 -0
  377. package/numkong/javascript/@numkong-linux-x64/package.json +25 -0
  378. package/numkong/javascript/@numkong-win32-arm64/package.json +25 -0
  379. package/numkong/javascript/@numkong-win32-x64/package.json +25 -0
  380. package/numkong/javascript/README.md +254 -0
  381. package/numkong/javascript/node-gyp-build.d.ts +4 -0
  382. package/numkong/javascript/numkong-browser.ts +40 -0
  383. package/numkong/javascript/numkong-wasm.ts +750 -0
  384. package/numkong/javascript/numkong.c +712 -0
  385. package/numkong/javascript/numkong.ts +613 -0
  386. package/{simsimd → numkong}/javascript/tsconfig-base.json +6 -3
  387. package/numkong/javascript/tsconfig-cjs.json +13 -0
  388. package/{simsimd → numkong}/javascript/tsconfig-esm.json +1 -1
  389. package/numkong/javascript/types.ts +784 -0
  390. package/numkong/numkong.gypi +104 -0
  391. package/numkong/package.json +108 -0
  392. package/numkong/probes/arm_neon.c +8 -0
  393. package/numkong/probes/arm_neon_bfdot.c +9 -0
  394. package/numkong/probes/arm_neon_fhm.c +9 -0
  395. package/numkong/probes/arm_neon_half.c +8 -0
  396. package/numkong/probes/arm_neon_sdot.c +9 -0
  397. package/numkong/probes/arm_neonfp8.c +9 -0
  398. package/numkong/probes/arm_sme.c +16 -0
  399. package/numkong/probes/arm_sme2.c +16 -0
  400. package/numkong/probes/arm_sme2p1.c +16 -0
  401. package/numkong/probes/arm_sme_bf16.c +16 -0
  402. package/numkong/probes/arm_sme_bi32.c +16 -0
  403. package/numkong/probes/arm_sme_f64.c +16 -0
  404. package/numkong/probes/arm_sme_fa64.c +14 -0
  405. package/numkong/probes/arm_sme_half.c +16 -0
  406. package/numkong/probes/arm_sme_lut2.c +15 -0
  407. package/numkong/probes/arm_sve.c +18 -0
  408. package/numkong/probes/arm_sve2.c +20 -0
  409. package/numkong/probes/arm_sve2p1.c +18 -0
  410. package/numkong/probes/arm_sve_bfdot.c +20 -0
  411. package/numkong/probes/arm_sve_half.c +18 -0
  412. package/numkong/probes/arm_sve_sdot.c +21 -0
  413. package/numkong/probes/loongarch_lasx.c +12 -0
  414. package/numkong/probes/power_vsx.c +12 -0
  415. package/numkong/probes/probe.js +127 -0
  416. package/numkong/probes/riscv_rvv.c +14 -0
  417. package/numkong/probes/riscv_rvv_bb.c +15 -0
  418. package/numkong/probes/riscv_rvv_bf16.c +17 -0
  419. package/numkong/probes/riscv_rvv_half.c +14 -0
  420. package/numkong/probes/wasm_v128relaxed.c +11 -0
  421. package/numkong/probes/x86_alder.c +17 -0
  422. package/numkong/probes/x86_diamond.c +17 -0
  423. package/numkong/probes/x86_genoa.c +17 -0
  424. package/numkong/probes/x86_graniteamx.c +19 -0
  425. package/numkong/probes/x86_haswell.c +11 -0
  426. package/numkong/probes/x86_icelake.c +17 -0
  427. package/numkong/probes/x86_sapphire.c +16 -0
  428. package/numkong/probes/x86_sapphireamx.c +18 -0
  429. package/numkong/probes/x86_sierra.c +17 -0
  430. package/numkong/probes/x86_skylake.c +15 -0
  431. package/numkong/probes/x86_turin.c +17 -0
  432. package/numkong/rust/README.md +698 -0
  433. package/numkong/rust/capabilities.rs +96 -0
  434. package/numkong/rust/cast.rs +314 -0
  435. package/numkong/rust/curved.rs +391 -0
  436. package/numkong/rust/each.rs +2721 -0
  437. package/numkong/rust/geospatial.rs +343 -0
  438. package/numkong/rust/matrix.rs +5424 -0
  439. package/numkong/rust/maxsim.rs +540 -0
  440. package/numkong/rust/mesh.rs +837 -0
  441. package/numkong/rust/numkong.rs +409 -0
  442. package/numkong/rust/probability.rs +272 -0
  443. package/numkong/rust/reduce.rs +1249 -0
  444. package/numkong/rust/set.rs +194 -0
  445. package/numkong/rust/sparse.rs +513 -0
  446. package/numkong/rust/spatial.rs +1815 -0
  447. package/numkong/rust/tensor.rs +7565 -0
  448. package/numkong/rust/types.rs +3666 -0
  449. package/numkong/rust/vector.rs +1899 -0
  450. package/numkong/rustfmt.toml +2 -0
  451. package/numkong/swift/Matrix.swift +1388 -0
  452. package/numkong/swift/MaxSim.swift +151 -0
  453. package/numkong/swift/NumKong.swift +283 -0
  454. package/numkong/swift/Numerics.swift +329 -0
  455. package/numkong/swift/README.md +433 -0
  456. package/numkong/swift/Scalar.swift +741 -0
  457. package/numkong/swift/Tensor.swift +209 -0
  458. package/numkong/test/README.md +315 -0
  459. package/numkong/test/playwright.config.ts +16 -0
  460. package/numkong/test/swift/Test.swift +534 -0
  461. package/numkong/test/test-browser-bundle-runner.mjs +36 -0
  462. package/numkong/test/test-browser-bundle.html +188 -0
  463. package/numkong/test/test-browser-runner.mjs +43 -0
  464. package/numkong/test/test-browser.html +305 -0
  465. package/numkong/test/test-dtypes.mjs +453 -0
  466. package/numkong/test/test-wasm.mjs +267 -0
  467. package/numkong/test/test.cpp +442 -0
  468. package/numkong/test/test.hpp +580 -0
  469. package/numkong/test/test.mjs +363 -0
  470. package/numkong/test/test_base.py +1041 -0
  471. package/numkong/test/test_cast.cpp +242 -0
  472. package/numkong/test/test_cross.hpp +551 -0
  473. package/numkong/test/test_cross.py +467 -0
  474. package/numkong/test/test_cross_amx.cpp +50 -0
  475. package/numkong/test/test_cross_arm.cpp +225 -0
  476. package/numkong/test/test_cross_blas.cpp +354 -0
  477. package/numkong/test/test_cross_loongarch.cpp +90 -0
  478. package/numkong/test/test_cross_power.cpp +73 -0
  479. package/numkong/test/test_cross_rvv.cpp +109 -0
  480. package/numkong/test/test_cross_serial.cpp +181 -0
  481. package/numkong/test/test_cross_sme.cpp +148 -0
  482. package/numkong/test/test_cross_wasm.cpp +141 -0
  483. package/numkong/test/test_cross_x86.cpp +442 -0
  484. package/numkong/test/test_curved.cpp +182 -0
  485. package/numkong/test/test_curved.py +207 -0
  486. package/numkong/test/test_dot.cpp +353 -0
  487. package/numkong/test/test_dot.py +296 -0
  488. package/numkong/test/test_each.cpp +437 -0
  489. package/numkong/test/test_each.py +756 -0
  490. package/numkong/test/test_geospatial.cpp +155 -0
  491. package/numkong/test/test_geospatial.py +241 -0
  492. package/numkong/test/test_maxsim.cpp +138 -0
  493. package/numkong/test/test_maxsim.py +209 -0
  494. package/numkong/test/test_mesh.cpp +203 -0
  495. package/numkong/test/test_mesh.py +394 -0
  496. package/numkong/test/test_probability.cpp +136 -0
  497. package/numkong/test/test_probability.py +166 -0
  498. package/numkong/test/test_reduce.cpp +563 -0
  499. package/numkong/test/test_reduce.py +781 -0
  500. package/numkong/test/test_set.cpp +151 -0
  501. package/numkong/test/test_set.py +120 -0
  502. package/numkong/test/test_similarities.py +579 -0
  503. package/numkong/test/test_sparse.cpp +121 -0
  504. package/numkong/test/test_sparse.py +118 -0
  505. package/numkong/test/test_spatial.cpp +518 -0
  506. package/numkong/test/test_spatial.py +269 -0
  507. package/numkong/test/test_tensor.cpp +884 -0
  508. package/numkong/test/test_tensor.py +1417 -0
  509. package/numkong/test/test_trigonometry.cpp +156 -0
  510. package/numkong/test/test_trigonometry.py +180 -0
  511. package/package.json +7 -8
  512. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  513. package/prebuilds/linux-arm64/usearch.node +0 -0
  514. package/prebuilds/linux-x64/usearch.node +0 -0
  515. package/fp16/CMakeLists.txt +0 -176
  516. package/fp16/LICENSE +0 -11
  517. package/fp16/README.md +0 -20
  518. package/fp16/bench/alt-element.cc +0 -89
  519. package/fp16/bench/from-alt-array.cc +0 -175
  520. package/fp16/bench/from-ieee-array.cc +0 -399
  521. package/fp16/bench/ieee-element.cc +0 -272
  522. package/fp16/bench/to-alt-array.cc +0 -91
  523. package/fp16/bench/to-ieee-array.cc +0 -303
  524. package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
  525. package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
  526. package/fp16/cmake/DownloadPSimd.cmake +0 -15
  527. package/fp16/configure.py +0 -65
  528. package/fp16/confu.yaml +0 -8
  529. package/fp16/include/fp16/__init__.py +0 -0
  530. package/fp16/include/fp16/avx.py +0 -50
  531. package/fp16/include/fp16/avx2.py +0 -53
  532. package/fp16/include/fp16/bitcasts.h +0 -92
  533. package/fp16/include/fp16/fp16.h +0 -451
  534. package/fp16/include/fp16/psimd.h +0 -131
  535. package/fp16/include/fp16.h +0 -11
  536. package/fp16/jni/Android.mk +0 -6
  537. package/fp16/jni/Application.mk +0 -4
  538. package/fp16/test/alt-from-fp32-value.cc +0 -498
  539. package/fp16/test/alt-to-fp32-bits.cc +0 -263
  540. package/fp16/test/alt-to-fp32-psimd.cc +0 -145
  541. package/fp16/test/alt-to-fp32-value.cc +0 -348
  542. package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
  543. package/fp16/test/bitcasts.cc +0 -56
  544. package/fp16/test/ieee-from-fp32-value.cc +0 -523
  545. package/fp16/test/ieee-to-fp32-bits.cc +0 -333
  546. package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
  547. package/fp16/test/ieee-to-fp32-value.cc +0 -420
  548. package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
  549. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
  550. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
  551. package/fp16/test/peachpy/stubs.py +0 -38
  552. package/fp16/test/tables.cc +0 -530
  553. package/fp16/test/tables.h +0 -13
  554. package/fp16/third-party/THHalf.h +0 -92
  555. package/fp16/third-party/eigen-half.h +0 -170
  556. package/fp16/third-party/float16-compressor.h +0 -80
  557. package/fp16/third-party/half.hpp +0 -2916
  558. package/fp16/third-party/npy-halffloat.h +0 -328
  559. package/simsimd/.git-blame-ignore-revs +0 -2
  560. package/simsimd/CONTRIBUTING.md +0 -321
  561. package/simsimd/Cargo.toml +0 -49
  562. package/simsimd/MANIFEST.in +0 -4
  563. package/simsimd/Package.swift +0 -28
  564. package/simsimd/README.md +0 -1493
  565. package/simsimd/binding.gyp +0 -35
  566. package/simsimd/build.rs +0 -70
  567. package/simsimd/c/lib.c +0 -374
  568. package/simsimd/include/module.modulemap +0 -4
  569. package/simsimd/include/simsimd/binary.h +0 -486
  570. package/simsimd/include/simsimd/curved.h +0 -1541
  571. package/simsimd/include/simsimd/dot.h +0 -1853
  572. package/simsimd/include/simsimd/elementwise.h +0 -2465
  573. package/simsimd/include/simsimd/geospatial.h +0 -43
  574. package/simsimd/include/simsimd/mesh.h +0 -69
  575. package/simsimd/include/simsimd/probability.h +0 -606
  576. package/simsimd/include/simsimd/simsimd.h +0 -2567
  577. package/simsimd/include/simsimd/sparse.h +0 -1384
  578. package/simsimd/include/simsimd/spatial.h +0 -2335
  579. package/simsimd/include/simsimd/types.h +0 -666
  580. package/simsimd/javascript/fallback.ts +0 -233
  581. package/simsimd/javascript/lib.c +0 -125
  582. package/simsimd/javascript/node-gyp-build.d.ts +0 -1
  583. package/simsimd/javascript/simsimd.ts +0 -179
  584. package/simsimd/javascript/tsconfig-cjs.json +0 -8
  585. package/simsimd/package-lock.json +0 -1761
  586. package/simsimd/package.json +0 -61
  587. package/simsimd/rust/lib.rs +0 -1821
  588. package/simsimd/scripts/bench.cxx +0 -1170
  589. package/simsimd/scripts/bench.ipynb +0 -187
  590. package/simsimd/scripts/bench.js +0 -127
  591. package/simsimd/scripts/bench.rs +0 -876
  592. package/simsimd/scripts/bench_vectors.py +0 -734
  593. package/simsimd/scripts/bench_vectors_live.py +0 -213
  594. package/simsimd/scripts/test.c +0 -187
  595. package/simsimd/scripts/test.mjs +0 -220
  596. package/simsimd/scripts/test.py +0 -1505
  597. package/simsimd/swift/SimSIMD.swift +0 -149
  598. package/simsimd/swift/Test.swift +0 -128
  599. /package/{simsimd → numkong}/CODE_OF_CONDUCT.md +0 -0
  600. /package/{simsimd → numkong}/LICENSE +0 -0
  601. /package/{simsimd → numkong}/javascript/dist-package-cjs.json +0 -0
  602. /package/{simsimd → numkong}/javascript/dist-package-esm.json +0 -0
@@ -0,0 +1,3785 @@
1
+ /**
2
+ * @brief AVX2 implementations for the redesigned reduction API (moments + minmax).
3
+ * @file include/numkong/reduce/haswell.h
4
+ * @author Ash Vardanian
5
+ * @date February 12, 2026
6
+ *
7
+ * @sa include/numkong/reduce.h
8
+ *
9
+ * @section reduce_block_caps Block-Cap Overflow Thresholds
10
+ *
11
+ * Dispatch functions use pairwise recursion when `count` exceeds a block cap.
12
+ * The cap is sized so the iteration counter in the contiguous kernel never wraps.
13
+ *
14
+ * Iteration counters start at 0 (initial load) and increment by 1 per SIMD chunk.
15
+ * A u8 counter holds 0..255 → 256 iterations → processes 256 × lanes elements.
16
+ * A u16 counter holds 0..65535 → 65536 iterations → processes 65536 × lanes elements.
17
+ * A u32 counter holds 0..4294967295 → ~4.3 billion iterations.
18
+ *
19
+ * Threshold formula: count > (COUNTER_MAX + 1) × lanes_per_chunk
20
+ * - u8 minmax: (NK_U8_MAX + 1) × lanes (e.g. 256 × 32 = 8192 for i8x32)
21
+ * - u16 minmax: (NK_U16_MAX + 1) × lanes (e.g. 65536 × 16 = 1048576 for i16x16)
22
+ * - u32 minmax: NK_U32_MAX × lanes (no +1: NK_U32_MAX + 1 overflows unsigned)
23
+ *
24
+ * Moments block caps are sized for accumulator overflow, not counter overflow.
25
+ * See individual dispatch functions for type-specific derivations.
26
+ */
27
+ #ifndef NK_REDUCE_HASWELL_H
28
+ #define NK_REDUCE_HASWELL_H
29
+
30
+ #if NK_TARGET_X8664_
31
+ #if NK_TARGET_HASWELL
32
+
33
+ #include "numkong/types.h"
34
+ #include "numkong/cast/haswell.h"
35
+ #include "numkong/reduce/serial.h"
36
+
37
+ #if defined(__cplusplus)
38
+ extern "C" {
39
+ #endif
40
+
41
+ #if defined(__clang__)
42
+ #pragma clang attribute push(__attribute__((target("avx2,f16c,fma,bmi,bmi2"))), apply_to = function)
43
+ #elif defined(__GNUC__)
44
+ #pragma GCC push_options
45
+ #pragma GCC target("avx2", "f16c", "fma", "bmi", "bmi2")
46
+ #endif
47
+
48
+ /** @brief Horizontal sum of 4 doubles in a YMM register. */
49
+ NK_INTERNAL nk_f64_t nk_reduce_add_f64x4_haswell_(__m256d sum_f64x4) {
50
+ __m128d low_f64x2 = _mm256_castpd256_pd128(sum_f64x4);
51
+ __m128d high_f64x2 = _mm256_extractf128_pd(sum_f64x4, 1);
52
+ __m128d sum_f64x2 = _mm_add_pd(low_f64x2, high_f64x2);
53
+ sum_f64x2 = _mm_hadd_pd(sum_f64x2, sum_f64x2);
54
+ return _mm_cvtsd_f64(sum_f64x2);
55
+ }
56
+
57
+ /** @brief Horizontal sum of 8 floats in a YMM register (native f32 precision). */
58
+ NK_INTERNAL nk_f32_t nk_reduce_add_f32x8_haswell_(__m256 sum_f32x8) {
59
+ __m128 low_f32x4 = _mm256_castps256_ps128(sum_f32x8);
60
+ __m128 high_f32x4 = _mm256_extractf128_ps(sum_f32x8, 1);
61
+ __m128 sum_f32x4 = _mm_add_ps(low_f32x4, high_f32x4);
62
+ sum_f32x4 = _mm_hadd_ps(sum_f32x4, sum_f32x4);
63
+ sum_f32x4 = _mm_hadd_ps(sum_f32x4, sum_f32x4);
64
+ return _mm_cvtss_f32(sum_f32x4);
65
+ }
66
+
67
+ /** @brief Horizontal sum of 8 i32s in a YMM register. */
68
+ NK_INTERNAL nk_i32_t nk_reduce_add_i32x8_haswell_(__m256i sum_i32x8) {
69
+ __m128i low_i32x4 = _mm256_castsi256_si128(sum_i32x8);
70
+ __m128i high_i32x4 = _mm256_extracti128_si256(sum_i32x8, 1);
71
+ __m128i sum_i32x4 = _mm_add_epi32(low_i32x4, high_i32x4);
72
+ sum_i32x4 = _mm_hadd_epi32(sum_i32x4, sum_i32x4);
73
+ sum_i32x4 = _mm_hadd_epi32(sum_i32x4, sum_i32x4);
74
+ return _mm_cvtsi128_si32(sum_i32x4);
75
+ }
76
+
77
+ /** @brief Horizontal sum of 4 i64s in a YMM register. */
78
+ NK_INTERNAL nk_i64_t nk_reduce_add_i64x4_haswell_(__m256i sum_i64x4) {
79
+ __m128i low_i64x2 = _mm256_castsi256_si128(sum_i64x4);
80
+ __m128i high_i64x2 = _mm256_extracti128_si256(sum_i64x4, 1);
81
+ __m128i sum_i64x2 = _mm_add_epi64(low_i64x2, high_i64x2);
82
+ __m128i high_lane_i64x2 = _mm_unpackhi_epi64(sum_i64x2, sum_i64x2);
83
+ __m128i final_i64x2 = _mm_add_epi64(sum_i64x2, high_lane_i64x2);
84
+ return _mm_cvtsi128_si64(final_i64x2);
85
+ }
86
+
87
+ /** @brief Horizontal min of 8 signed i8s in a YMM register. */
88
+ NK_INTERNAL nk_i8_t nk_reduce_min_i8x32_haswell_(__m256i min_i8x32) {
89
+ __m128i low_i8x16 = _mm256_castsi256_si128(min_i8x32);
90
+ __m128i high_i8x16 = _mm256_extracti128_si256(min_i8x32, 1);
91
+ __m128i min_i8x16 = _mm_min_epi8(low_i8x16, high_i8x16);
92
+ min_i8x16 = _mm_min_epi8(min_i8x16, _mm_shuffle_epi32(min_i8x16, _MM_SHUFFLE(2, 3, 0, 1)));
93
+ min_i8x16 = _mm_min_epi8(min_i8x16, _mm_shuffle_epi32(min_i8x16, _MM_SHUFFLE(1, 0, 3, 2)));
94
+ min_i8x16 = _mm_min_epi8(min_i8x16, _mm_srli_si128(min_i8x16, 2));
95
+ min_i8x16 = _mm_min_epi8(min_i8x16, _mm_srli_si128(min_i8x16, 1));
96
+ return (nk_i8_t)_mm_cvtsi128_si32(min_i8x16);
97
+ }
98
+
99
+ /** @brief Horizontal max of 8 signed i8s in a YMM register. */
100
+ NK_INTERNAL nk_i8_t nk_reduce_max_i8x32_haswell_(__m256i max_i8x32) {
101
+ __m128i low_i8x16 = _mm256_castsi256_si128(max_i8x32);
102
+ __m128i high_i8x16 = _mm256_extracti128_si256(max_i8x32, 1);
103
+ __m128i max_i8x16 = _mm_max_epi8(low_i8x16, high_i8x16);
104
+ max_i8x16 = _mm_max_epi8(max_i8x16, _mm_shuffle_epi32(max_i8x16, _MM_SHUFFLE(2, 3, 0, 1)));
105
+ max_i8x16 = _mm_max_epi8(max_i8x16, _mm_shuffle_epi32(max_i8x16, _MM_SHUFFLE(1, 0, 3, 2)));
106
+ max_i8x16 = _mm_max_epi8(max_i8x16, _mm_srli_si128(max_i8x16, 2));
107
+ max_i8x16 = _mm_max_epi8(max_i8x16, _mm_srli_si128(max_i8x16, 1));
108
+ return (nk_i8_t)_mm_cvtsi128_si32(max_i8x16);
109
+ }
110
+
111
+ /** @brief Horizontal min of 8 unsigned u8s in a YMM register. */
112
+ NK_INTERNAL nk_u8_t nk_reduce_min_u8x32_haswell_(__m256i min_u8x32) {
113
+ __m128i low_u8x16 = _mm256_castsi256_si128(min_u8x32);
114
+ __m128i high_u8x16 = _mm256_extracti128_si256(min_u8x32, 1);
115
+ __m128i min_u8x16 = _mm_min_epu8(low_u8x16, high_u8x16);
116
+ min_u8x16 = _mm_min_epu8(min_u8x16, _mm_shuffle_epi32(min_u8x16, _MM_SHUFFLE(2, 3, 0, 1)));
117
+ min_u8x16 = _mm_min_epu8(min_u8x16, _mm_shuffle_epi32(min_u8x16, _MM_SHUFFLE(1, 0, 3, 2)));
118
+ min_u8x16 = _mm_min_epu8(min_u8x16, _mm_srli_si128(min_u8x16, 2));
119
+ min_u8x16 = _mm_min_epu8(min_u8x16, _mm_srli_si128(min_u8x16, 1));
120
+ return (nk_u8_t)_mm_cvtsi128_si32(min_u8x16);
121
+ }
122
+
123
+ /** @brief Horizontal max of 8 unsigned u8s in a YMM register. */
124
+ NK_INTERNAL nk_u8_t nk_reduce_max_u8x32_haswell_(__m256i max_u8x32) {
125
+ __m128i low_u8x16 = _mm256_castsi256_si128(max_u8x32);
126
+ __m128i high_u8x16 = _mm256_extracti128_si256(max_u8x32, 1);
127
+ __m128i max_u8x16 = _mm_max_epu8(low_u8x16, high_u8x16);
128
+ max_u8x16 = _mm_max_epu8(max_u8x16, _mm_shuffle_epi32(max_u8x16, _MM_SHUFFLE(2, 3, 0, 1)));
129
+ max_u8x16 = _mm_max_epu8(max_u8x16, _mm_shuffle_epi32(max_u8x16, _MM_SHUFFLE(1, 0, 3, 2)));
130
+ max_u8x16 = _mm_max_epu8(max_u8x16, _mm_srli_si128(max_u8x16, 2));
131
+ max_u8x16 = _mm_max_epu8(max_u8x16, _mm_srli_si128(max_u8x16, 1));
132
+ return (nk_u8_t)_mm_cvtsi128_si32(max_u8x16);
133
+ }
134
+
135
+ /** @brief Horizontal min of 16 signed i16s in a YMM register. */
136
+ NK_INTERNAL nk_i16_t nk_reduce_min_i16x16_haswell_(__m256i min_i16x16) {
137
+ __m128i low_i16x8 = _mm256_castsi256_si128(min_i16x16);
138
+ __m128i high_i16x8 = _mm256_extracti128_si256(min_i16x16, 1);
139
+ __m128i min_i16x8 = _mm_min_epi16(low_i16x8, high_i16x8);
140
+ min_i16x8 = _mm_min_epi16(min_i16x8, _mm_shuffle_epi32(min_i16x8, _MM_SHUFFLE(2, 3, 0, 1)));
141
+ min_i16x8 = _mm_min_epi16(min_i16x8, _mm_shuffle_epi32(min_i16x8, _MM_SHUFFLE(1, 0, 3, 2)));
142
+ min_i16x8 = _mm_min_epi16(min_i16x8, _mm_srli_si128(min_i16x8, 2));
143
+ return (nk_i16_t)_mm_cvtsi128_si32(min_i16x8);
144
+ }
145
+
146
+ /** @brief Horizontal max of 16 signed i16s in a YMM register. */
147
+ NK_INTERNAL nk_i16_t nk_reduce_max_i16x16_haswell_(__m256i max_i16x16) {
148
+ __m128i low_i16x8 = _mm256_castsi256_si128(max_i16x16);
149
+ __m128i high_i16x8 = _mm256_extracti128_si256(max_i16x16, 1);
150
+ __m128i max_i16x8 = _mm_max_epi16(low_i16x8, high_i16x8);
151
+ max_i16x8 = _mm_max_epi16(max_i16x8, _mm_shuffle_epi32(max_i16x8, _MM_SHUFFLE(2, 3, 0, 1)));
152
+ max_i16x8 = _mm_max_epi16(max_i16x8, _mm_shuffle_epi32(max_i16x8, _MM_SHUFFLE(1, 0, 3, 2)));
153
+ max_i16x8 = _mm_max_epi16(max_i16x8, _mm_srli_si128(max_i16x8, 2));
154
+ return (nk_i16_t)_mm_cvtsi128_si32(max_i16x8);
155
+ }
156
+
157
+ /** @brief Horizontal min of 16 unsigned u16s in a YMM register. */
158
+ NK_INTERNAL nk_u16_t nk_reduce_min_u16x16_haswell_(__m256i min_u16x16) {
159
+ __m128i low_u16x8 = _mm256_castsi256_si128(min_u16x16);
160
+ __m128i high_u16x8 = _mm256_extracti128_si256(min_u16x16, 1);
161
+ __m128i min_u16x8 = _mm_min_epu16(low_u16x8, high_u16x8);
162
+ min_u16x8 = _mm_min_epu16(min_u16x8, _mm_shuffle_epi32(min_u16x8, _MM_SHUFFLE(2, 3, 0, 1)));
163
+ min_u16x8 = _mm_min_epu16(min_u16x8, _mm_shuffle_epi32(min_u16x8, _MM_SHUFFLE(1, 0, 3, 2)));
164
+ min_u16x8 = _mm_min_epu16(min_u16x8, _mm_srli_si128(min_u16x8, 2));
165
+ return (nk_u16_t)_mm_cvtsi128_si32(min_u16x8);
166
+ }
167
+
168
+ /** @brief Horizontal max of 16 unsigned u16s in a YMM register. */
169
+ NK_INTERNAL nk_u16_t nk_reduce_max_u16x16_haswell_(__m256i max_u16x16) {
170
+ __m128i low_u16x8 = _mm256_castsi256_si128(max_u16x16);
171
+ __m128i high_u16x8 = _mm256_extracti128_si256(max_u16x16, 1);
172
+ __m128i max_u16x8 = _mm_max_epu16(low_u16x8, high_u16x8);
173
+ max_u16x8 = _mm_max_epu16(max_u16x8, _mm_shuffle_epi32(max_u16x8, _MM_SHUFFLE(2, 3, 0, 1)));
174
+ max_u16x8 = _mm_max_epu16(max_u16x8, _mm_shuffle_epi32(max_u16x8, _MM_SHUFFLE(1, 0, 3, 2)));
175
+ max_u16x8 = _mm_max_epu16(max_u16x8, _mm_srli_si128(max_u16x8, 2));
176
+ return (nk_u16_t)_mm_cvtsi128_si32(max_u16x8);
177
+ }
178
+
179
+ /** @brief Horizontal min of 8 signed i32s in a YMM register. */
180
+ NK_INTERNAL nk_i32_t nk_reduce_min_i32x8_haswell_(__m256i min_i32x8) {
181
+ __m128i low_i32x4 = _mm256_castsi256_si128(min_i32x8);
182
+ __m128i high_i32x4 = _mm256_extracti128_si256(min_i32x8, 1);
183
+ __m128i min_i32x4 = _mm_min_epi32(low_i32x4, high_i32x4);
184
+ min_i32x4 = _mm_min_epi32(min_i32x4, _mm_shuffle_epi32(min_i32x4, _MM_SHUFFLE(2, 3, 0, 1)));
185
+ min_i32x4 = _mm_min_epi32(min_i32x4, _mm_shuffle_epi32(min_i32x4, _MM_SHUFFLE(1, 0, 3, 2)));
186
+ return _mm_cvtsi128_si32(min_i32x4);
187
+ }
188
+
189
+ /** @brief Horizontal max of 8 signed i32s in a YMM register. */
190
+ NK_INTERNAL nk_i32_t nk_reduce_max_i32x8_haswell_(__m256i max_i32x8) {
191
+ __m128i low_i32x4 = _mm256_castsi256_si128(max_i32x8);
192
+ __m128i high_i32x4 = _mm256_extracti128_si256(max_i32x8, 1);
193
+ __m128i max_i32x4 = _mm_max_epi32(low_i32x4, high_i32x4);
194
+ max_i32x4 = _mm_max_epi32(max_i32x4, _mm_shuffle_epi32(max_i32x4, _MM_SHUFFLE(2, 3, 0, 1)));
195
+ max_i32x4 = _mm_max_epi32(max_i32x4, _mm_shuffle_epi32(max_i32x4, _MM_SHUFFLE(1, 0, 3, 2)));
196
+ return _mm_cvtsi128_si32(max_i32x4);
197
+ }
198
+
199
+ /** @brief Horizontal min of 8 unsigned u32s in a YMM register. */
200
+ NK_INTERNAL nk_u32_t nk_reduce_min_u32x8_haswell_(__m256i min_u32x8) {
201
+ __m128i low_u32x4 = _mm256_castsi256_si128(min_u32x8);
202
+ __m128i high_u32x4 = _mm256_extracti128_si256(min_u32x8, 1);
203
+ __m128i min_u32x4 = _mm_min_epu32(low_u32x4, high_u32x4);
204
+ min_u32x4 = _mm_min_epu32(min_u32x4, _mm_shuffle_epi32(min_u32x4, _MM_SHUFFLE(2, 3, 0, 1)));
205
+ min_u32x4 = _mm_min_epu32(min_u32x4, _mm_shuffle_epi32(min_u32x4, _MM_SHUFFLE(1, 0, 3, 2)));
206
+ return (nk_u32_t)_mm_cvtsi128_si32(min_u32x4);
207
+ }
208
+
209
+ /** @brief Horizontal max of 8 unsigned u32s in a YMM register. */
210
+ NK_INTERNAL nk_u32_t nk_reduce_max_u32x8_haswell_(__m256i max_u32x8) {
211
+ __m128i low_u32x4 = _mm256_castsi256_si128(max_u32x8);
212
+ __m128i high_u32x4 = _mm256_extracti128_si256(max_u32x8, 1);
213
+ __m128i max_u32x4 = _mm_max_epu32(low_u32x4, high_u32x4);
214
+ max_u32x4 = _mm_max_epu32(max_u32x4, _mm_shuffle_epi32(max_u32x4, _MM_SHUFFLE(2, 3, 0, 1)));
215
+ max_u32x4 = _mm_max_epu32(max_u32x4, _mm_shuffle_epi32(max_u32x4, _MM_SHUFFLE(1, 0, 3, 2)));
216
+ return (nk_u32_t)_mm_cvtsi128_si32(max_u32x4);
217
+ }
218
+
219
+ /** @brief Horizontal min of 4 signed i64s in a YMM register using comparison+blend. */
220
+ NK_INTERNAL nk_i64_t nk_reduce_min_i64x4_haswell_(__m256i min_i64x4) {
221
+ __m128i low_i64x2 = _mm256_castsi256_si128(min_i64x4);
222
+ __m128i high_i64x2 = _mm256_extracti128_si256(min_i64x4, 1);
223
+ __m128i cmp_i64x2 = _mm_cmpgt_epi64(low_i64x2, high_i64x2);
224
+ __m128i min_i64x2 = _mm_blendv_epi8(low_i64x2, high_i64x2, cmp_i64x2);
225
+ __m128i high_lane_i64x2 = _mm_unpackhi_epi64(min_i64x2, min_i64x2);
226
+ __m128i cmp_final_i64x2 = _mm_cmpgt_epi64(min_i64x2, high_lane_i64x2);
227
+ __m128i result_i64x2 = _mm_blendv_epi8(min_i64x2, high_lane_i64x2, cmp_final_i64x2);
228
+ return _mm_cvtsi128_si64(result_i64x2);
229
+ }
230
+
231
+ /** @brief Horizontal max of 4 signed i64s in a YMM register using comparison+blend. */
232
+ NK_INTERNAL nk_i64_t nk_reduce_max_i64x4_haswell_(__m256i max_i64x4) {
233
+ __m128i low_i64x2 = _mm256_castsi256_si128(max_i64x4);
234
+ __m128i high_i64x2 = _mm256_extracti128_si256(max_i64x4, 1);
235
+ __m128i cmp_i64x2 = _mm_cmpgt_epi64(low_i64x2, high_i64x2);
236
+ __m128i max_i64x2 = _mm_blendv_epi8(high_i64x2, low_i64x2, cmp_i64x2);
237
+ __m128i high_lane_i64x2 = _mm_unpackhi_epi64(max_i64x2, max_i64x2);
238
+ __m128i cmp_final_i64x2 = _mm_cmpgt_epi64(max_i64x2, high_lane_i64x2);
239
+ __m128i result_i64x2 = _mm_blendv_epi8(high_lane_i64x2, max_i64x2, cmp_final_i64x2);
240
+ return _mm_cvtsi128_si64(result_i64x2);
241
+ }
242
+
243
+ /** @brief Horizontal min of 4 unsigned u64s in a YMM register using XOR trick for unsigned comparison. */
244
+ NK_INTERNAL nk_u64_t nk_reduce_min_u64x4_haswell_(__m256i min_u64x4) {
245
+ __m128i sign_bit_i64x2 = _mm_set1_epi64x((nk_i64_t)0x8000000000000000ull);
246
+ __m128i low_u64x2 = _mm256_castsi256_si128(min_u64x4);
247
+ __m128i high_u64x2 = _mm256_extracti128_si256(min_u64x4, 1);
248
+ __m128i cmp_i64x2 = _mm_cmpgt_epi64(_mm_xor_si128(low_u64x2, sign_bit_i64x2),
249
+ _mm_xor_si128(high_u64x2, sign_bit_i64x2));
250
+ __m128i min_u64x2 = _mm_blendv_epi8(low_u64x2, high_u64x2, cmp_i64x2);
251
+ __m128i high_lane_u64x2 = _mm_unpackhi_epi64(min_u64x2, min_u64x2);
252
+ __m128i cmp_final_i64x2 = _mm_cmpgt_epi64(_mm_xor_si128(min_u64x2, sign_bit_i64x2),
253
+ _mm_xor_si128(high_lane_u64x2, sign_bit_i64x2));
254
+ __m128i result_u64x2 = _mm_blendv_epi8(min_u64x2, high_lane_u64x2, cmp_final_i64x2);
255
+ return (nk_u64_t)_mm_cvtsi128_si64(result_u64x2);
256
+ }
257
+
258
+ /** @brief Horizontal max of 4 unsigned u64s in a YMM register using XOR trick for unsigned comparison. */
259
+ NK_INTERNAL nk_u64_t nk_reduce_max_u64x4_haswell_(__m256i max_u64x4) {
260
+ __m128i sign_bit_i64x2 = _mm_set1_epi64x((nk_i64_t)0x8000000000000000ull);
261
+ __m128i low_u64x2 = _mm256_castsi256_si128(max_u64x4);
262
+ __m128i high_u64x2 = _mm256_extracti128_si256(max_u64x4, 1);
263
+ __m128i cmp_i64x2 = _mm_cmpgt_epi64(_mm_xor_si128(low_u64x2, sign_bit_i64x2),
264
+ _mm_xor_si128(high_u64x2, sign_bit_i64x2));
265
+ __m128i max_u64x2 = _mm_blendv_epi8(high_u64x2, low_u64x2, cmp_i64x2);
266
+ __m128i high_lane_u64x2 = _mm_unpackhi_epi64(max_u64x2, max_u64x2);
267
+ __m128i cmp_final_i64x2 = _mm_cmpgt_epi64(_mm_xor_si128(max_u64x2, sign_bit_i64x2),
268
+ _mm_xor_si128(high_lane_u64x2, sign_bit_i64x2));
269
+ __m128i result_u64x2 = _mm_blendv_epi8(high_lane_u64x2, max_u64x2, cmp_final_i64x2);
270
+ return (nk_u64_t)_mm_cvtsi128_si64(result_u64x2);
271
+ }
272
+
273
+ /** @brief Horizontal min of 8 floats in a YMM register. */
274
+ NK_INTERNAL nk_f32_t nk_reduce_min_f32x8_haswell_(__m256 min_f32x8) {
275
+ __m128 low_f32x4 = _mm256_castps256_ps128(min_f32x8);
276
+ __m128 high_f32x4 = _mm256_extractf128_ps(min_f32x8, 1);
277
+ __m128 min_f32x4 = _mm_min_ps(low_f32x4, high_f32x4);
278
+ min_f32x4 = _mm_min_ps(min_f32x4, _mm_shuffle_ps(min_f32x4, min_f32x4, _MM_SHUFFLE(2, 3, 0, 1)));
279
+ min_f32x4 = _mm_min_ps(min_f32x4, _mm_shuffle_ps(min_f32x4, min_f32x4, _MM_SHUFFLE(1, 0, 3, 2)));
280
+ return _mm_cvtss_f32(min_f32x4);
281
+ }
282
+
283
+ /** @brief Horizontal max of 8 floats in a YMM register. */
284
+ NK_INTERNAL nk_f32_t nk_reduce_max_f32x8_haswell_(__m256 max_f32x8) {
285
+ __m128 low_f32x4 = _mm256_castps256_ps128(max_f32x8);
286
+ __m128 high_f32x4 = _mm256_extractf128_ps(max_f32x8, 1);
287
+ __m128 max_f32x4 = _mm_max_ps(low_f32x4, high_f32x4);
288
+ max_f32x4 = _mm_max_ps(max_f32x4, _mm_shuffle_ps(max_f32x4, max_f32x4, _MM_SHUFFLE(2, 3, 0, 1)));
289
+ max_f32x4 = _mm_max_ps(max_f32x4, _mm_shuffle_ps(max_f32x4, max_f32x4, _MM_SHUFFLE(1, 0, 3, 2)));
290
+ return _mm_cvtss_f32(max_f32x4);
291
+ }
292
+
293
+ /** @brief Horizontal min of 4 doubles in a YMM register. */
294
+ NK_INTERNAL nk_f64_t nk_reduce_min_f64x4_haswell_(__m256d min_f64x4) {
295
+ __m128d low_f64x2 = _mm256_castpd256_pd128(min_f64x4);
296
+ __m128d high_f64x2 = _mm256_extractf128_pd(min_f64x4, 1);
297
+ __m128d min_f64x2 = _mm_min_pd(low_f64x2, high_f64x2);
298
+ min_f64x2 = _mm_min_pd(min_f64x2, _mm_shuffle_pd(min_f64x2, min_f64x2, 1));
299
+ return _mm_cvtsd_f64(min_f64x2);
300
+ }
301
+
302
+ /** @brief Horizontal max of 4 doubles in a YMM register. */
303
+ NK_INTERNAL nk_f64_t nk_reduce_max_f64x4_haswell_(__m256d max_f64x4) {
304
+ __m128d low_f64x2 = _mm256_castpd256_pd128(max_f64x4);
305
+ __m128d high_f64x2 = _mm256_extractf128_pd(max_f64x4, 1);
306
+ __m128d max_f64x2 = _mm_max_pd(low_f64x2, high_f64x2);
307
+ max_f64x2 = _mm_max_pd(max_f64x2, _mm_shuffle_pd(max_f64x2, max_f64x2, 1));
308
+ return _mm_cvtsd_f64(max_f64x2);
309
+ }
310
+
311
+ NK_INTERNAL __m256i nk_fp8x32_to_u8x32_comparable_haswell_(__m256i raw_i8x32) {
312
+ // In AVX2, use signed comparison: 0 > x means x < 0 (negative)
313
+ __m256i neg_i8x32 = _mm256_cmpgt_epi8(_mm256_setzero_si256(), raw_i8x32);
314
+ __m256i pos_xor_i8x32 = _mm256_set1_epi8((char)0x80);
315
+ __m256i neg_xor_i8x32 = _mm256_set1_epi8((char)0xFF);
316
+ __m256i xor_i8x32 = _mm256_blendv_epi8(pos_xor_i8x32, neg_xor_i8x32, neg_i8x32);
317
+ return _mm256_xor_si256(raw_i8x32, xor_i8x32);
318
+ }
319
+
320
+ NK_INTERNAL __m256i nk_u8x32_comparable_to_fp8x32_haswell_(__m256i cmp_i8x32) {
321
+ // Values < 0x80 were negative FP8 (sign bit clear in comparable form), values >= 0x80 were positive
322
+ __m256i sign_bit_i8x32 = _mm256_set1_epi8((char)0x80);
323
+ __m256i was_neg_i8x32 = _mm256_cmpeq_epi8(_mm256_and_si256(cmp_i8x32, sign_bit_i8x32), _mm256_setzero_si256());
324
+ __m256i neg_xor_i8x32 = _mm256_set1_epi8((char)0xFF);
325
+ __m256i pos_xor_i8x32 = _mm256_set1_epi8((char)0x80);
326
+ __m256i xor_i8x32 = _mm256_blendv_epi8(pos_xor_i8x32, neg_xor_i8x32, was_neg_i8x32);
327
+ return _mm256_xor_si256(cmp_i8x32, xor_i8x32);
328
+ }
329
+
330
+ /** @brief Horizontal argmin: returns index of first minimum unsigned byte in YMM register. */
331
+ NK_INTERNAL nk_size_t nk_argmin_u8x32_haswell_(__m256i data_u8x32) {
332
+ nk_u8_t min_val = nk_reduce_min_u8x32_haswell_(data_u8x32);
333
+ __m256i eq_i8x32 = _mm256_cmpeq_epi8(data_u8x32, _mm256_set1_epi8((char)min_val));
334
+ int eq_bits = _mm256_movemask_epi8(eq_i8x32);
335
+ return (nk_size_t)_tzcnt_u32((unsigned int)eq_bits);
336
+ }
337
+
338
+ /** @brief Horizontal argmax: returns index of first maximum unsigned byte in YMM register. */
339
+ NK_INTERNAL nk_size_t nk_argmax_u8x32_haswell_(__m256i data_u8x32) {
340
+ nk_u8_t max_val = nk_reduce_max_u8x32_haswell_(data_u8x32);
341
+ __m256i eq_i8x32 = _mm256_cmpeq_epi8(data_u8x32, _mm256_set1_epi8((char)max_val));
342
+ int eq_bits = _mm256_movemask_epi8(eq_i8x32);
343
+ return (nk_size_t)_tzcnt_u32((unsigned int)eq_bits);
344
+ }
345
+
346
+ NK_INTERNAL __m256i nk_bf16x16_to_comparable_i16x16_haswell_(__m256i raw_u16x16) {
347
+ __m256i sign_i16x16 = _mm256_srai_epi16(raw_u16x16, 15);
348
+ __m256i flip_i16x16 = _mm256_srli_epi16(sign_i16x16, 1);
349
+ return _mm256_xor_si256(raw_u16x16, flip_i16x16);
350
+ }
351
+
352
+ NK_INTERNAL __m256i nk_f16x16_to_comparable_i16x16_haswell_(__m256i raw_u16x16) {
353
+ __m256i sign_i16x16 = _mm256_srai_epi16(raw_u16x16, 15);
354
+ __m256i flip_i16x16 = _mm256_srli_epi16(sign_i16x16, 1);
355
+ return _mm256_xor_si256(raw_u16x16, flip_i16x16);
356
+ }
357
+
358
+ NK_INTERNAL __m256i nk_u64_sadd_epi64_haswell_(__m256i a_u64x4, __m256i b_u64x4) {
359
+ __m256i result_u64x4 = _mm256_add_epi64(a_u64x4, b_u64x4);
360
+ // Unsigned overflow: result < a. AVX2 only has signed cmpgt, so flip sign bits.
361
+ __m256i sign_bit_i64x4 = _mm256_set1_epi64x((nk_i64_t)0x8000000000000000ULL);
362
+ __m256i a_biased_i64x4 = _mm256_xor_si256(a_u64x4, sign_bit_i64x4);
363
+ __m256i result_biased_i64x4 = _mm256_xor_si256(result_u64x4, sign_bit_i64x4);
364
+ __m256i overflow_u64x4 = _mm256_cmpgt_epi64(a_biased_i64x4, result_biased_i64x4);
365
+ return _mm256_or_si256(result_u64x4, overflow_u64x4); // overflow lanes -> all-ones = U64_MAX
366
+ }
367
+
368
+ NK_INTERNAL __m256i nk_i64_smul_sq_epi64_haswell_(__m256i val_i64x4) {
369
+ // abs(val) — AVX2 lacks _mm256_abs_epi64, emulate:
370
+ __m256i sign_i64x4 = _mm256_cmpgt_epi64(_mm256_setzero_si256(), val_i64x4);
371
+ __m256i abs_val_u64x4 = _mm256_sub_epi64(_mm256_xor_si256(val_i64x4, sign_i64x4), sign_i64x4);
372
+ // Extract low 32 bits and square: _mm256_mul_epu32 multiplies even 32-bit lanes -> 64-bit
373
+ __m256i low_halves_u32x4 = _mm256_and_si256(abs_val_u64x4, _mm256_set1_epi64x(0xFFFFFFFF));
374
+ __m256i low_squared_u64x4 = _mm256_mul_epu32(low_halves_u32x4, low_halves_u32x4);
375
+ // Check if high 32 bits are zero (value fits in 32 bits)
376
+ __m256i high_bits_u64x4 = _mm256_srli_epi64(abs_val_u64x4, 32);
377
+ __m256i is_small_u64x4 = _mm256_cmpeq_epi64(high_bits_u64x4, _mm256_setzero_si256());
378
+ // Saturate: I64_MAX when overflow (since result is always positive)
379
+ __m256i saturated_u64x4 = _mm256_set1_epi64x(NK_I64_MAX);
380
+ return _mm256_blendv_epi8(saturated_u64x4, low_squared_u64x4, is_small_u64x4);
381
+ }
382
+
383
+ NK_INTERNAL __m256i nk_u64_smul_sq_epi64_haswell_(__m256i val_u64x4) {
384
+ __m256i low_halves_u32x4 = _mm256_and_si256(val_u64x4, _mm256_set1_epi64x(0xFFFFFFFF));
385
+ __m256i low_squared_u64x4 = _mm256_mul_epu32(low_halves_u32x4, low_halves_u32x4);
386
+ __m256i high_bits_u64x4 = _mm256_srli_epi64(val_u64x4, 32);
387
+ __m256i is_small_u64x4 = _mm256_cmpeq_epi64(high_bits_u64x4, _mm256_setzero_si256());
388
+ __m256i saturated_u64x4 = _mm256_set1_epi64x((nk_i64_t)-1);
389
+ return _mm256_blendv_epi8(saturated_u64x4, low_squared_u64x4, is_small_u64x4);
390
+ }
391
+
392
+ NK_INTERNAL nk_u64_t nk_reduce_sadd_u64x4_haswell_(__m256i v_u64x4) {
393
+ // 4->2: fold high 128 into low 128
394
+ __m128i high_u64x2 = _mm256_extracti128_si256(v_u64x4, 1);
395
+ __m128i low_u64x2 = _mm256_castsi256_si128(v_u64x4);
396
+ __m128i sum_u64x2 = _mm_add_epi64(low_u64x2, high_u64x2);
397
+ __m128i sign_bit_i64x2 = _mm_set1_epi64x((nk_i64_t)0x8000000000000000ULL);
398
+ __m128i low_biased_i64x2 = _mm_xor_si128(low_u64x2, sign_bit_i64x2);
399
+ __m128i sum_biased_i64x2 = _mm_xor_si128(sum_u64x2, sign_bit_i64x2);
400
+ __m128i overflow_u64x2 = _mm_cmpgt_epi64(low_biased_i64x2, sum_biased_i64x2);
401
+ sum_u64x2 = _mm_or_si128(sum_u64x2, overflow_u64x2);
402
+ // 2->1: fold lane 1 into lane 0
403
+ __m128i swapped_u64x2 = _mm_unpackhi_epi64(sum_u64x2, sum_u64x2);
404
+ __m128i final_u64x2 = _mm_add_epi64(sum_u64x2, swapped_u64x2);
405
+ __m128i sum2_biased_i64x2 = _mm_xor_si128(sum_u64x2, sign_bit_i64x2);
406
+ __m128i final_biased_i64x2 = _mm_xor_si128(final_u64x2, sign_bit_i64x2);
407
+ __m128i overflow2_u64x2 = _mm_cmpgt_epi64(sum2_biased_i64x2, final_biased_i64x2);
408
+ final_u64x2 = _mm_or_si128(final_u64x2, overflow2_u64x2);
409
+ return (nk_u64_t)_mm_cvtsi128_si64(final_u64x2);
410
+ }
411
+
412
+ NK_INTERNAL __m256i nk_stride_blend_u1x32_(nk_size_t stride) {
413
+ switch (stride) {
414
+ case 2:
415
+ return _mm256_setr_epi8(-1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1,
416
+ 0, -1, 0, -1, 0, -1, 0);
417
+ case 3:
418
+ return _mm256_setr_epi8(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
419
+ 0, -1, 0, 0, -1, 0);
420
+ case 4:
421
+ return _mm256_setr_epi8(-1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0,
422
+ 0, -1, 0, 0, 0);
423
+ case 5:
424
+ return _mm256_setr_epi8(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0,
425
+ 0, 0, 0, -1, 0);
426
+ case 6:
427
+ return _mm256_setr_epi8(-1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0,
428
+ 0, 0, -1, 0);
429
+ case 7:
430
+ return _mm256_setr_epi8(-1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
431
+ -1, 0, 0, 0);
432
+ case 8:
433
+ return _mm256_setr_epi8(-1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0,
434
+ 0, 0, 0, 0);
435
+ default: return _mm256_setzero_si256();
436
+ }
437
+ }
438
+
439
+ NK_INTERNAL __m256i nk_stride_blend_b16x16_(nk_size_t stride) {
440
+ switch (stride) {
441
+ case 2: return _mm256_setr_epi16(-1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0);
442
+ case 3: return _mm256_setr_epi16(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
443
+ case 4: return _mm256_setr_epi16(-1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0);
444
+ case 5: return _mm256_setr_epi16(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1);
445
+ case 6: return _mm256_setr_epi16(-1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0);
446
+ case 7: return _mm256_setr_epi16(-1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, -1, 0);
447
+ case 8: return _mm256_setr_epi16(-1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0);
448
+ default: return _mm256_setzero_si256();
449
+ }
450
+ }
451
+
452
+ NK_INTERNAL __m256i nk_stride_blend_b32x8_(nk_size_t stride) {
453
+ switch (stride) {
454
+ case 2: return _mm256_setr_epi32(-1, 0, -1, 0, -1, 0, -1, 0); // 4 elems
455
+ case 3: return _mm256_setr_epi32(-1, 0, 0, -1, 0, 0, -1, 0); // 3 elems
456
+ case 4: return _mm256_setr_epi32(-1, 0, 0, 0, -1, 0, 0, 0); // 2 elems
457
+ case 5: return _mm256_setr_epi32(-1, 0, 0, 0, 0, -1, 0, 0); // 2 elems
458
+ case 6: return _mm256_setr_epi32(-1, 0, 0, 0, 0, 0, -1, 0); // 2 elems
459
+ case 7: return _mm256_setr_epi32(-1, 0, 0, 0, 0, 0, 0, -1); // 2 elems
460
+ case 8: return _mm256_setr_epi32(-1, 0, 0, 0, 0, 0, 0, 0); // 1 elem
461
+ default: return _mm256_setzero_si256();
462
+ }
463
+ }
464
+
465
+ NK_INTERNAL __m256i nk_stride_blend_b64x4_(nk_size_t stride) {
466
+ switch (stride) {
467
+ case 2: return _mm256_setr_epi64x(-1, 0, -1, 0); // 2 elems
468
+ case 3: return _mm256_setr_epi64x(-1, 0, 0, -1); // 2 elems (wraps)
469
+ case 4: return _mm256_setr_epi64x(-1, 0, 0, 0); // 1 elem
470
+ default: return _mm256_setr_epi64x(-1, 0, 0, 0); // 1 elem for stride 5+
471
+ }
472
+ }
473
+
474
+ NK_INTERNAL nk_size_t nk_stride_elems_b32x8_(nk_size_t stride) {
475
+ switch (stride) {
476
+ case 2: return 4;
477
+ case 3: return 3;
478
+ case 4: return 2;
479
+ case 5: return 2;
480
+ case 6: return 2;
481
+ case 7: return 2;
482
+ case 8: return 1;
483
+ default: return 0;
484
+ }
485
+ }
486
+
487
+ NK_INTERNAL nk_size_t nk_stride_elems_b64x4_(nk_size_t stride) {
488
+ switch (stride) {
489
+ case 2: return 2;
490
+ case 3: return 2;
491
+ case 4: return 1;
492
+ default: return 1;
493
+ }
494
+ }
495
+
496
+ NK_INTERNAL void nk_reduce_moments_f32_haswell_contiguous_( //
497
+ nk_f32_t const *data_ptr, nk_size_t count, //
498
+ nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
499
+
500
+ __m256d sum_low_f64x4 = _mm256_setzero_pd(), sum_high_f64x4 = _mm256_setzero_pd();
501
+ __m256d sumsq_low_f64x4 = _mm256_setzero_pd(), sumsq_high_f64x4 = _mm256_setzero_pd();
502
+ nk_size_t idx = 0;
503
+ for (; idx + 8 <= count; idx += 8) {
504
+ __m256d low_f64x4 = _mm256_cvtps_pd(_mm_loadu_ps(data_ptr + idx));
505
+ __m256d high_f64x4 = _mm256_cvtps_pd(_mm_loadu_ps(data_ptr + idx + 4));
506
+ sum_low_f64x4 = _mm256_add_pd(sum_low_f64x4, low_f64x4);
507
+ sum_high_f64x4 = _mm256_add_pd(sum_high_f64x4, high_f64x4);
508
+ sumsq_low_f64x4 = _mm256_fmadd_pd(low_f64x4, low_f64x4, sumsq_low_f64x4);
509
+ sumsq_high_f64x4 = _mm256_fmadd_pd(high_f64x4, high_f64x4, sumsq_high_f64x4);
510
+ }
511
+ __m256d sum_f64x4 = _mm256_add_pd(sum_low_f64x4, sum_high_f64x4);
512
+ __m256d sumsq_f64x4 = _mm256_add_pd(sumsq_low_f64x4, sumsq_high_f64x4);
513
+ nk_f64_t sum = nk_reduce_add_f64x4_haswell_(sum_f64x4);
514
+ nk_f64_t sumsq = nk_reduce_add_f64x4_haswell_(sumsq_f64x4);
515
+ for (; idx < count; ++idx) {
516
+ nk_f64_t val = (nk_f64_t)data_ptr[idx];
517
+ sum += val, sumsq += val * val;
518
+ }
519
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
520
+ }
521
+
522
+ NK_INTERNAL void nk_reduce_moments_f32_haswell_strided_( //
523
+ nk_f32_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
524
+ nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
525
+
526
+ __m256i blend_mask_i32x8 = nk_stride_blend_b32x8_(stride_elements);
527
+ __m128 blend_low_f32x4 = _mm_castsi128_ps(_mm256_castsi256_si128(blend_mask_i32x8));
528
+ __m128 blend_high_f32x4 = _mm_castsi128_ps(_mm256_extracti128_si256(blend_mask_i32x8, 1));
529
+ __m128 zero_f32x4 = _mm_setzero_ps();
530
+ __m256d sum_low_f64x4 = _mm256_setzero_pd(), sum_high_f64x4 = _mm256_setzero_pd();
531
+ __m256d sumsq_low_f64x4 = _mm256_setzero_pd(), sumsq_high_f64x4 = _mm256_setzero_pd();
532
+ nk_size_t idx = 0, total = count * stride_elements;
533
+ nk_size_t step = nk_size_round_up_to_multiple_(8, stride_elements);
534
+ for (; idx + stride_elements + 7 <= total; idx += step) {
535
+ __m128 low_f32x4 = _mm_blendv_ps(zero_f32x4, _mm_loadu_ps(data_ptr + idx), blend_low_f32x4);
536
+ __m128 high_f32x4 = _mm_blendv_ps(zero_f32x4, _mm_loadu_ps(data_ptr + idx + 4), blend_high_f32x4);
537
+ __m256d low_f64x4 = _mm256_cvtps_pd(low_f32x4);
538
+ __m256d high_f64x4 = _mm256_cvtps_pd(high_f32x4);
539
+ sum_low_f64x4 = _mm256_add_pd(sum_low_f64x4, low_f64x4);
540
+ sum_high_f64x4 = _mm256_add_pd(sum_high_f64x4, high_f64x4);
541
+ sumsq_low_f64x4 = _mm256_fmadd_pd(low_f64x4, low_f64x4, sumsq_low_f64x4);
542
+ sumsq_high_f64x4 = _mm256_fmadd_pd(high_f64x4, high_f64x4, sumsq_high_f64x4);
543
+ }
544
+ __m256d sum_f64x4 = _mm256_add_pd(sum_low_f64x4, sum_high_f64x4);
545
+ __m256d sumsq_f64x4 = _mm256_add_pd(sumsq_low_f64x4, sumsq_high_f64x4);
546
+ nk_f64_t sum = nk_reduce_add_f64x4_haswell_(sum_f64x4);
547
+ nk_f64_t sumsq = nk_reduce_add_f64x4_haswell_(sumsq_f64x4);
548
+ nk_f32_t const *ptr = data_ptr + idx;
549
+ nk_size_t remaining = count - idx / stride_elements;
550
+ for (nk_size_t i = 0; i < remaining; ++i, ptr += stride_elements) {
551
+ nk_f64_t val = (nk_f64_t)(*ptr);
552
+ sum += val, sumsq += val * val;
553
+ }
554
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
555
+ }
556
+
557
+ NK_INTERNAL void nk_reduce_moments_f32_haswell_gather_( //
558
+ nk_f32_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
559
+ nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
560
+
561
+ nk_i32_t stride_elements = (nk_i32_t)(stride_bytes / sizeof(nk_f32_t));
562
+ __m256i indices_i32x8 = _mm256_mullo_epi32(_mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7),
563
+ _mm256_set1_epi32(stride_elements));
564
+ __m256d sum_f64x4 = _mm256_setzero_pd();
565
+ __m256d sumsq_f64x4 = _mm256_setzero_pd();
566
+ nk_size_t idx = 0;
567
+ for (; idx + 8 <= count; idx += 8) {
568
+ __m256 gathered_f32x8 = _mm256_i32gather_ps(data_ptr + idx * stride_elements, indices_i32x8, sizeof(nk_f32_t));
569
+ __m256d low_f64x4 = _mm256_cvtps_pd(_mm256_castps256_ps128(gathered_f32x8));
570
+ __m256d high_f64x4 = _mm256_cvtps_pd(_mm256_extractf128_ps(gathered_f32x8, 1));
571
+ sum_f64x4 = _mm256_add_pd(sum_f64x4, low_f64x4);
572
+ sum_f64x4 = _mm256_add_pd(sum_f64x4, high_f64x4);
573
+ sumsq_f64x4 = _mm256_fmadd_pd(low_f64x4, low_f64x4, sumsq_f64x4);
574
+ sumsq_f64x4 = _mm256_fmadd_pd(high_f64x4, high_f64x4, sumsq_f64x4);
575
+ }
576
+ nk_f64_t sum = nk_reduce_add_f64x4_haswell_(sum_f64x4);
577
+ nk_f64_t sumsq = nk_reduce_add_f64x4_haswell_(sumsq_f64x4);
578
+ unsigned char const *ptr = (unsigned char const *)(data_ptr + idx * stride_elements);
579
+ for (; idx < count; ++idx, ptr += stride_bytes) {
580
+ nk_f64_t val = (nk_f64_t)(*(nk_f32_t const *)ptr);
581
+ sum += val, sumsq += val * val;
582
+ }
583
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
584
+ }
585
+
586
+ NK_PUBLIC void nk_reduce_moments_f32_haswell( //
587
+ nk_f32_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
588
+ nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
589
+
590
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_f32_t);
591
+ int aligned = (stride_bytes % sizeof(nk_f32_t) == 0);
592
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
593
+ else if (!aligned) nk_reduce_moments_f32_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
594
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 8) {
595
+ nk_size_t left_count = count / 2;
596
+ nk_f64_t left_sum, left_sumsq, right_sum, right_sumsq;
597
+ nk_reduce_moments_f32_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
598
+ nk_reduce_moments_f32_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
599
+ &right_sum, &right_sumsq);
600
+ *sum_ptr = left_sum + right_sum, *sumsq_ptr = left_sumsq + right_sumsq;
601
+ }
602
+ else if (stride_elements == 1) nk_reduce_moments_f32_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
603
+ else if (stride_elements <= 8)
604
+ nk_reduce_moments_f32_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
605
+ else nk_reduce_moments_f32_haswell_gather_(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
606
+ }
607
+
608
+ NK_INTERNAL void nk_reduce_minmax_f32_haswell_contiguous_( //
609
+ nk_f32_t const *data_ptr, nk_size_t count, //
610
+ nk_f32_t *min_value_ptr, nk_size_t *min_index_ptr, //
611
+ nk_f32_t *max_value_ptr, nk_size_t *max_index_ptr) {
612
+
613
+ __m256 min_f32x8 = _mm256_set1_ps(NK_F32_MAX);
614
+ __m256 max_f32x8 = _mm256_set1_ps(NK_F32_MIN);
615
+ __m256i min_loop_cycle_u32x8 = _mm256_setzero_si256();
616
+ __m256i max_loop_cycle_u32x8 = _mm256_setzero_si256();
617
+ __m256i current_loop_cycle_u32x8 = _mm256_setzero_si256();
618
+ __m256i one_u32x8 = _mm256_set1_epi32(1);
619
+
620
+ nk_size_t idx = 0;
621
+ for (; idx + 8 <= count; idx += 8) {
622
+ __m256 data_f32x8 = _mm256_loadu_ps(data_ptr + idx);
623
+ __m256 less_b32x8 = _mm256_cmp_ps(data_f32x8, min_f32x8, _CMP_LT_OQ);
624
+ __m256 greater_b32x8 = _mm256_cmp_ps(data_f32x8, max_f32x8, _CMP_GT_OQ);
625
+ min_f32x8 = _mm256_blendv_ps(min_f32x8, data_f32x8, less_b32x8);
626
+ max_f32x8 = _mm256_blendv_ps(max_f32x8, data_f32x8, greater_b32x8);
627
+ min_loop_cycle_u32x8 = _mm256_blendv_epi8(min_loop_cycle_u32x8, current_loop_cycle_u32x8,
628
+ _mm256_castps_si256(less_b32x8));
629
+ max_loop_cycle_u32x8 = _mm256_blendv_epi8(max_loop_cycle_u32x8, current_loop_cycle_u32x8,
630
+ _mm256_castps_si256(greater_b32x8));
631
+ current_loop_cycle_u32x8 = _mm256_add_epi32(current_loop_cycle_u32x8, one_u32x8);
632
+ }
633
+
634
+ // Reduce SIMD lanes and scan for indices
635
+ nk_f32_t min_value = NK_F32_MAX, max_value = NK_F32_MIN;
636
+ nk_size_t min_idx = NK_SIZE_MAX, max_idx = NK_SIZE_MAX;
637
+
638
+ // Locate the minimum index
639
+ if (idx > 0) min_value = nk_reduce_min_f32x8_haswell_(min_f32x8);
640
+ if (min_value < NK_F32_MAX) {
641
+ __m256 value_match_b32x8 = _mm256_cmp_ps(min_f32x8, _mm256_set1_ps(min_value), _CMP_EQ_OQ);
642
+ __m256i masked_cycle_u32x8 = _mm256_blendv_epi8(_mm256_set1_epi32((int)NK_U32_MAX), min_loop_cycle_u32x8,
643
+ _mm256_castps_si256(value_match_b32x8));
644
+ nk_u32_t earliest_loop_cycle = nk_reduce_min_u32x8_haswell_(masked_cycle_u32x8);
645
+ __m256i cycle_match_b32x8 = _mm256_cmpeq_epi32(masked_cycle_u32x8, _mm256_set1_epi32((int)earliest_loop_cycle));
646
+ unsigned int min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_ps(_mm256_castsi256_ps(cycle_match_b32x8)));
647
+ nk_b256_vec_t loop_cycle_vec;
648
+ loop_cycle_vec.ymm = min_loop_cycle_u32x8;
649
+ min_idx = (nk_size_t)loop_cycle_vec.u32s[min_lane] * 8 + min_lane;
650
+ }
651
+ // Locate the maximum index
652
+ if (idx > 0) max_value = nk_reduce_max_f32x8_haswell_(max_f32x8);
653
+ if (max_value > NK_F32_MIN) {
654
+ __m256 value_match_b32x8 = _mm256_cmp_ps(max_f32x8, _mm256_set1_ps(max_value), _CMP_EQ_OQ);
655
+ __m256i masked_cycle_u32x8 = _mm256_blendv_epi8(_mm256_set1_epi32((int)NK_U32_MAX), max_loop_cycle_u32x8,
656
+ _mm256_castps_si256(value_match_b32x8));
657
+ nk_u32_t earliest_loop_cycle = nk_reduce_min_u32x8_haswell_(masked_cycle_u32x8);
658
+ __m256i cycle_match_b32x8 = _mm256_cmpeq_epi32(masked_cycle_u32x8, _mm256_set1_epi32((int)earliest_loop_cycle));
659
+ unsigned int max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_ps(_mm256_castsi256_ps(cycle_match_b32x8)));
660
+ nk_b256_vec_t loop_cycle_vec;
661
+ loop_cycle_vec.ymm = max_loop_cycle_u32x8;
662
+ max_idx = (nk_size_t)loop_cycle_vec.u32s[max_lane] * 8 + max_lane;
663
+ }
664
+
665
+ // Scalar tail
666
+ for (; idx < count; ++idx) {
667
+ nk_f32_t val = data_ptr[idx];
668
+ if (val < min_value) min_value = val, min_idx = idx;
669
+ if (val > max_value) max_value = val, max_idx = idx;
670
+ }
671
+ *min_value_ptr = min_value, *min_index_ptr = min_idx;
672
+ *max_value_ptr = max_value, *max_index_ptr = max_idx;
673
+ }
674
+
675
+ NK_PUBLIC void nk_reduce_minmax_f32_haswell( //
676
+ nk_f32_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
677
+ nk_f32_t *min_value_ptr, nk_size_t *min_index_ptr, //
678
+ nk_f32_t *max_value_ptr, nk_size_t *max_index_ptr) {
679
+
680
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_f32_t);
681
+ int aligned = (stride_bytes % sizeof(nk_f32_t) == 0);
682
+ if (count == 0)
683
+ *min_value_ptr = NK_F32_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_F32_MIN,
684
+ *max_index_ptr = NK_SIZE_MAX;
685
+ else if (!aligned)
686
+ nk_reduce_minmax_f32_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
687
+ max_index_ptr);
688
+ else if (count > (nk_size_t)NK_U32_MAX * 8) {
689
+ nk_size_t left_count = count / 2;
690
+ nk_f32_t left_min, right_min, left_max, right_max;
691
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
692
+ nk_reduce_minmax_f32_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
693
+ &left_max_index);
694
+ nk_reduce_minmax_f32_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
695
+ &right_min, &right_min_index, &right_max, &right_max_index);
696
+ if (right_min < left_min) *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
697
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
698
+ if (right_max > left_max) *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
699
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
700
+ }
701
+ else if (stride_elements == 1)
702
+ nk_reduce_minmax_f32_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
703
+ max_index_ptr);
704
+ else
705
+ nk_reduce_minmax_f32_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
706
+ max_index_ptr);
707
+ }
708
+
709
+ NK_INTERNAL void nk_reduce_moments_f64_haswell_contiguous_( //
710
+ nk_f64_t const *data_ptr, nk_size_t count, //
711
+ nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
712
+
713
+ __m256d sum_f64x4 = _mm256_setzero_pd();
714
+ __m256d sum_comp_f64x4 = _mm256_setzero_pd();
715
+ __m256d sumsq_f64x4 = _mm256_setzero_pd();
716
+ __m256d sumsq_comp_f64x4 = _mm256_setzero_pd();
717
+ nk_size_t idx = 0;
718
+ for (; idx + 4 <= count; idx += 4) {
719
+ __m256d val_f64x4 = _mm256_loadu_pd(data_ptr + idx);
720
+ __m256d tentative_f64x4 = _mm256_add_pd(sum_f64x4, val_f64x4);
721
+ __m256d round_f64x4 = _mm256_sub_pd(tentative_f64x4, sum_f64x4);
722
+ __m256d corr_f64x4 = _mm256_add_pd(_mm256_sub_pd(sum_f64x4, _mm256_sub_pd(tentative_f64x4, round_f64x4)),
723
+ _mm256_sub_pd(val_f64x4, round_f64x4));
724
+ sum_comp_f64x4 = _mm256_add_pd(sum_comp_f64x4, corr_f64x4);
725
+ sum_f64x4 = tentative_f64x4;
726
+ __m256d sq_f64x4 = _mm256_mul_pd(val_f64x4, val_f64x4);
727
+ __m256d tentative_sq_f64x4 = _mm256_add_pd(sumsq_f64x4, sq_f64x4);
728
+ __m256d round_sq_f64x4 = _mm256_sub_pd(tentative_sq_f64x4, sumsq_f64x4);
729
+ __m256d corr_sq_f64x4 = _mm256_add_pd(
730
+ _mm256_sub_pd(sumsq_f64x4, _mm256_sub_pd(tentative_sq_f64x4, round_sq_f64x4)),
731
+ _mm256_sub_pd(sq_f64x4, round_sq_f64x4));
732
+ sumsq_comp_f64x4 = _mm256_add_pd(sumsq_comp_f64x4, corr_sq_f64x4);
733
+ sumsq_f64x4 = tentative_sq_f64x4;
734
+ }
735
+ nk_size_t remaining = count - idx;
736
+ if (remaining > 0) {
737
+ nk_b256_vec_t tail_vec;
738
+ nk_partial_load_b64x4_serial_(data_ptr + idx, &tail_vec, remaining);
739
+ __m256d val_f64x4 = tail_vec.ymm_pd;
740
+ __m256d tentative_f64x4 = _mm256_add_pd(sum_f64x4, val_f64x4);
741
+ __m256d round_f64x4 = _mm256_sub_pd(tentative_f64x4, sum_f64x4);
742
+ __m256d corr_f64x4 = _mm256_add_pd(_mm256_sub_pd(sum_f64x4, _mm256_sub_pd(tentative_f64x4, round_f64x4)),
743
+ _mm256_sub_pd(val_f64x4, round_f64x4));
744
+ sum_comp_f64x4 = _mm256_add_pd(sum_comp_f64x4, corr_f64x4);
745
+ sum_f64x4 = tentative_f64x4;
746
+ __m256d sq_f64x4 = _mm256_mul_pd(val_f64x4, val_f64x4);
747
+ __m256d tentative_sq_f64x4 = _mm256_add_pd(sumsq_f64x4, sq_f64x4);
748
+ __m256d round_sq_f64x4 = _mm256_sub_pd(tentative_sq_f64x4, sumsq_f64x4);
749
+ __m256d corr_sq_f64x4 = _mm256_add_pd(
750
+ _mm256_sub_pd(sumsq_f64x4, _mm256_sub_pd(tentative_sq_f64x4, round_sq_f64x4)),
751
+ _mm256_sub_pd(sq_f64x4, round_sq_f64x4));
752
+ sumsq_comp_f64x4 = _mm256_add_pd(sumsq_comp_f64x4, corr_sq_f64x4);
753
+ sumsq_f64x4 = tentative_sq_f64x4;
754
+ }
755
+ *sum_ptr = nk_reduce_add_f64x4_haswell_(_mm256_add_pd(sum_f64x4, sum_comp_f64x4)),
756
+ *sumsq_ptr = nk_reduce_add_f64x4_haswell_(_mm256_add_pd(sumsq_f64x4, sumsq_comp_f64x4));
757
+ }
758
+
759
+ NK_INTERNAL void nk_reduce_moments_f64_haswell_strided_( //
760
+ nk_f64_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
761
+ nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
762
+
763
+ __m256i blend_mask_i64x4 = nk_stride_blend_b64x4_(stride_elements);
764
+ __m256d blend_f64x4 = _mm256_castsi256_pd(blend_mask_i64x4);
765
+ __m256d zero_f64x4 = _mm256_setzero_pd();
766
+ __m256d sum_f64x4 = _mm256_setzero_pd();
767
+ __m256d sum_comp_f64x4 = _mm256_setzero_pd();
768
+ __m256d sumsq_f64x4 = _mm256_setzero_pd();
769
+ __m256d sumsq_comp_f64x4 = _mm256_setzero_pd();
770
+ nk_size_t idx = 0, total = count * stride_elements;
771
+ nk_size_t step = nk_size_round_up_to_multiple_(4, stride_elements);
772
+ for (; idx + stride_elements + 3 <= total; idx += step) {
773
+ __m256d val_f64x4 = _mm256_blendv_pd(zero_f64x4, _mm256_loadu_pd(data_ptr + idx), blend_f64x4);
774
+ __m256d tentative_f64x4 = _mm256_add_pd(sum_f64x4, val_f64x4);
775
+ __m256d round_f64x4 = _mm256_sub_pd(tentative_f64x4, sum_f64x4);
776
+ __m256d corr_f64x4 = _mm256_add_pd(_mm256_sub_pd(sum_f64x4, _mm256_sub_pd(tentative_f64x4, round_f64x4)),
777
+ _mm256_sub_pd(val_f64x4, round_f64x4));
778
+ sum_comp_f64x4 = _mm256_add_pd(sum_comp_f64x4, corr_f64x4);
779
+ sum_f64x4 = tentative_f64x4;
780
+ __m256d sq_f64x4 = _mm256_mul_pd(val_f64x4, val_f64x4);
781
+ __m256d tentative_sq_f64x4 = _mm256_add_pd(sumsq_f64x4, sq_f64x4);
782
+ __m256d round_sq_f64x4 = _mm256_sub_pd(tentative_sq_f64x4, sumsq_f64x4);
783
+ __m256d corr_sq_f64x4 = _mm256_add_pd(
784
+ _mm256_sub_pd(sumsq_f64x4, _mm256_sub_pd(tentative_sq_f64x4, round_sq_f64x4)),
785
+ _mm256_sub_pd(sq_f64x4, round_sq_f64x4));
786
+ sumsq_comp_f64x4 = _mm256_add_pd(sumsq_comp_f64x4, corr_sq_f64x4);
787
+ sumsq_f64x4 = tentative_sq_f64x4;
788
+ }
789
+ nk_f64_t sum = nk_reduce_add_f64x4_haswell_(_mm256_add_pd(sum_f64x4, sum_comp_f64x4));
790
+ nk_f64_t sumsq = nk_reduce_add_f64x4_haswell_(_mm256_add_pd(sumsq_f64x4, sumsq_comp_f64x4));
791
+ nk_f64_t const *ptr = data_ptr + idx;
792
+ nk_size_t remaining_elements = count - idx / stride_elements;
793
+ for (nk_size_t i = 0; i < remaining_elements; ++i, ptr += stride_elements) {
794
+ nk_f64_t val = *ptr;
795
+ sum += val, sumsq += val * val;
796
+ }
797
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
798
+ }
799
+
800
+ NK_PUBLIC void nk_reduce_moments_f64_haswell( //
801
+ nk_f64_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
802
+ nk_f64_t *sum_ptr, nk_f64_t *sumsq_ptr) {
803
+
804
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_f64_t);
805
+ int aligned = (stride_bytes % sizeof(nk_f64_t) == 0);
806
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
807
+ else if (!aligned) nk_reduce_moments_f64_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
808
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 4) {
809
+ nk_size_t left_count = count / 2;
810
+ nk_f64_t left_sum, left_sumsq, right_sum, right_sumsq;
811
+ nk_reduce_moments_f64_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
812
+ nk_reduce_moments_f64_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
813
+ &right_sum, &right_sumsq);
814
+ *sum_ptr = left_sum + right_sum, *sumsq_ptr = left_sumsq + right_sumsq;
815
+ }
816
+ else if (stride_elements == 1) nk_reduce_moments_f64_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
817
+ else if (stride_elements <= 4)
818
+ nk_reduce_moments_f64_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
819
+ else nk_reduce_moments_f64_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
820
+ }
821
+
822
+ NK_INTERNAL void nk_reduce_minmax_f64_haswell_contiguous_( //
823
+ nk_f64_t const *data_ptr, nk_size_t count, //
824
+ nk_f64_t *min_value_ptr, nk_size_t *min_index_ptr, //
825
+ nk_f64_t *max_value_ptr, nk_size_t *max_index_ptr) {
826
+
827
+ __m256d min_f64x4 = _mm256_set1_pd(NK_F64_MAX);
828
+ __m256d max_f64x4 = _mm256_set1_pd(NK_F64_MIN);
829
+ __m256i min_loop_cycle_u64x4 = _mm256_setzero_si256();
830
+ __m256i max_loop_cycle_u64x4 = _mm256_setzero_si256();
831
+ __m256i current_loop_cycle_u64x4 = _mm256_setzero_si256();
832
+ __m256i one_u64x4 = _mm256_set1_epi64x(1);
833
+
834
+ nk_size_t idx = 0;
835
+ for (; idx + 4 <= count; idx += 4) {
836
+ __m256d data_f64x4 = _mm256_loadu_pd(data_ptr + idx);
837
+ __m256d less_b64x4 = _mm256_cmp_pd(data_f64x4, min_f64x4, _CMP_LT_OQ);
838
+ __m256d greater_b64x4 = _mm256_cmp_pd(data_f64x4, max_f64x4, _CMP_GT_OQ);
839
+ min_f64x4 = _mm256_blendv_pd(min_f64x4, data_f64x4, less_b64x4);
840
+ max_f64x4 = _mm256_blendv_pd(max_f64x4, data_f64x4, greater_b64x4);
841
+ min_loop_cycle_u64x4 = _mm256_blendv_epi8(min_loop_cycle_u64x4, current_loop_cycle_u64x4,
842
+ _mm256_castpd_si256(less_b64x4));
843
+ max_loop_cycle_u64x4 = _mm256_blendv_epi8(max_loop_cycle_u64x4, current_loop_cycle_u64x4,
844
+ _mm256_castpd_si256(greater_b64x4));
845
+ current_loop_cycle_u64x4 = _mm256_add_epi64(current_loop_cycle_u64x4, one_u64x4);
846
+ }
847
+
848
+ nk_size_t remaining = count - idx;
849
+ if (remaining > 0) {
850
+ nk_b256_vec_t tail_vec;
851
+ nk_partial_load_b64x4_serial_(data_ptr + idx, &tail_vec, remaining);
852
+ __m256i lane_indices_i64x4 = _mm256_setr_epi64x(0, 1, 2, 3);
853
+ __m256i threshold_i64x4 = _mm256_set1_epi64x((nk_i64_t)remaining);
854
+ __m256i valid_i64x4 = _mm256_cmpgt_epi64(threshold_i64x4, lane_indices_i64x4);
855
+ __m256d valid_b64x4 = _mm256_castsi256_pd(valid_i64x4);
856
+ __m256d nan_f64x4 = _mm256_castsi256_pd(_mm256_set1_epi64x((long long)0x7FF8000000000000LL));
857
+ __m256d data_f64x4 = _mm256_blendv_pd(nan_f64x4, tail_vec.ymm_pd, valid_b64x4);
858
+ __m256d less_b64x4 = _mm256_cmp_pd(data_f64x4, min_f64x4, _CMP_LT_OQ);
859
+ __m256d greater_b64x4 = _mm256_cmp_pd(data_f64x4, max_f64x4, _CMP_GT_OQ);
860
+ min_f64x4 = _mm256_blendv_pd(min_f64x4, data_f64x4, less_b64x4);
861
+ max_f64x4 = _mm256_blendv_pd(max_f64x4, data_f64x4, greater_b64x4);
862
+ min_loop_cycle_u64x4 = _mm256_blendv_epi8(min_loop_cycle_u64x4, current_loop_cycle_u64x4,
863
+ _mm256_castpd_si256(less_b64x4));
864
+ max_loop_cycle_u64x4 = _mm256_blendv_epi8(max_loop_cycle_u64x4, current_loop_cycle_u64x4,
865
+ _mm256_castpd_si256(greater_b64x4));
866
+ }
867
+
868
+ nk_f64_t min_value = nk_reduce_min_f64x4_haswell_(min_f64x4);
869
+ nk_f64_t max_value = nk_reduce_max_f64x4_haswell_(max_f64x4);
870
+ if (min_value == NK_F64_MAX && max_value == NK_F64_MIN) {
871
+ *min_value_ptr = NK_F64_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_F64_MIN,
872
+ *max_index_ptr = NK_SIZE_MAX;
873
+ return;
874
+ }
875
+ {
876
+ __m256d value_match_b64x4 = _mm256_cmp_pd(min_f64x4, _mm256_set1_pd(min_value), _CMP_EQ_OQ);
877
+ __m256i masked_cycle_u64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x((nk_i64_t)NK_U64_MAX), min_loop_cycle_u64x4,
878
+ _mm256_castpd_si256(value_match_b64x4));
879
+ nk_u64_t earliest_loop_cycle = nk_reduce_min_u64x4_haswell_(masked_cycle_u64x4);
880
+ __m256i cycle_match_b64x4 = _mm256_cmpeq_epi64(masked_cycle_u64x4,
881
+ _mm256_set1_epi64x((nk_i64_t)earliest_loop_cycle));
882
+ unsigned int min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_pd(_mm256_castsi256_pd(cycle_match_b64x4)));
883
+ nk_b256_vec_t loop_cycle_vec;
884
+ loop_cycle_vec.ymm = min_loop_cycle_u64x4;
885
+ *min_value_ptr = min_value;
886
+ *min_index_ptr = (nk_size_t)loop_cycle_vec.u64s[min_lane] * 4 + min_lane;
887
+ }
888
+ {
889
+ __m256d value_match_b64x4 = _mm256_cmp_pd(max_f64x4, _mm256_set1_pd(max_value), _CMP_EQ_OQ);
890
+ __m256i masked_cycle_u64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x((nk_i64_t)NK_U64_MAX), max_loop_cycle_u64x4,
891
+ _mm256_castpd_si256(value_match_b64x4));
892
+ nk_u64_t earliest_loop_cycle = nk_reduce_min_u64x4_haswell_(masked_cycle_u64x4);
893
+ __m256i cycle_match_b64x4 = _mm256_cmpeq_epi64(masked_cycle_u64x4,
894
+ _mm256_set1_epi64x((nk_i64_t)earliest_loop_cycle));
895
+ unsigned int max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_pd(_mm256_castsi256_pd(cycle_match_b64x4)));
896
+ nk_b256_vec_t loop_cycle_vec;
897
+ loop_cycle_vec.ymm = max_loop_cycle_u64x4;
898
+ *max_value_ptr = max_value;
899
+ *max_index_ptr = (nk_size_t)loop_cycle_vec.u64s[max_lane] * 4 + max_lane;
900
+ }
901
+ }
902
+
903
+ NK_PUBLIC void nk_reduce_minmax_f64_haswell( //
904
+ nk_f64_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
905
+ nk_f64_t *min_value_ptr, nk_size_t *min_index_ptr, //
906
+ nk_f64_t *max_value_ptr, nk_size_t *max_index_ptr) {
907
+
908
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_f64_t);
909
+ int aligned = (stride_bytes % sizeof(nk_f64_t) == 0);
910
+ if (count == 0)
911
+ *min_value_ptr = NK_F64_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_F64_MIN,
912
+ *max_index_ptr = NK_SIZE_MAX;
913
+ else if (!aligned)
914
+ nk_reduce_minmax_f64_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
915
+ max_index_ptr);
916
+ else if (stride_elements == 1)
917
+ nk_reduce_minmax_f64_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
918
+ max_index_ptr);
919
+ else
920
+ nk_reduce_minmax_f64_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
921
+ max_index_ptr);
922
+ }
923
+
924
+ NK_INTERNAL void nk_reduce_moments_i8_haswell_contiguous_( //
925
+ nk_i8_t const *data_ptr, nk_size_t count, //
926
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
927
+
928
+ __m256i bias_i8x32 = _mm256_set1_epi8((char)0x80);
929
+ __m256i zero_i8x32 = _mm256_setzero_si256();
930
+ __m256i sum_u64x4 = _mm256_setzero_si256();
931
+ __m256i sumsq_low_i32x8 = _mm256_setzero_si256();
932
+ __m256i sumsq_high_i32x8 = _mm256_setzero_si256();
933
+ nk_size_t idx = 0;
934
+ for (; idx + 32 <= count; idx += 32) {
935
+ __m256i data_i8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
936
+ __m256i unsigned_u8x32 = _mm256_xor_si256(data_i8x32, bias_i8x32);
937
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(unsigned_u8x32, zero_i8x32));
938
+ __m256i low_i16x16 = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(data_i8x32));
939
+ __m256i high_i16x16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(data_i8x32, 1));
940
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, _mm256_madd_epi16(low_i16x16, low_i16x16));
941
+ sumsq_high_i32x8 = _mm256_add_epi32(sumsq_high_i32x8, _mm256_madd_epi16(high_i16x16, high_i16x16));
942
+ }
943
+ nk_size_t remaining = count - idx;
944
+ if (remaining > 0) {
945
+ nk_b256_vec_t tail_vec;
946
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
947
+ __m256i data_i8x32 = tail_vec.ymm;
948
+ // Build masked bias: only bias valid lanes so zero-padded lanes stay zero
949
+ __m256i lane_indices_u8x32 = _mm256_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
950
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
951
+ __m256i valid_b8x32 = _mm256_cmpgt_epi8(_mm256_set1_epi8((char)remaining), lane_indices_u8x32);
952
+ __m256i masked_bias_i8x32 = _mm256_and_si256(bias_i8x32, valid_b8x32);
953
+ __m256i unsigned_u8x32 = _mm256_xor_si256(data_i8x32, masked_bias_i8x32);
954
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(unsigned_u8x32, zero_i8x32));
955
+ __m256i low_i16x16 = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(data_i8x32));
956
+ __m256i high_i16x16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(data_i8x32, 1));
957
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, _mm256_madd_epi16(low_i16x16, low_i16x16));
958
+ sumsq_high_i32x8 = _mm256_add_epi32(sumsq_high_i32x8, _mm256_madd_epi16(high_i16x16, high_i16x16));
959
+ }
960
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, sumsq_high_i32x8);
961
+ __m256i sumsq_i64x4 = _mm256_cvtepi32_epi64(_mm256_castsi256_si128(sumsq_low_i32x8));
962
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_extracti128_si256(sumsq_low_i32x8, 1)));
963
+ nk_i64_t sum = (nk_i64_t)(nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4);
964
+ sum -= (nk_i64_t)128 * (nk_i64_t)count;
965
+ nk_u64_t sumsq = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_i64x4);
966
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
967
+ }
968
+
969
+ NK_INTERNAL void nk_reduce_moments_i8_haswell_strided_( //
970
+ nk_i8_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
971
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
972
+
973
+ __m256i stride_mask_i8x32 = nk_stride_blend_u1x32_(stride_elements);
974
+ nk_size_t elements_per_vector = nk_size_divide_round_up_(32, stride_elements);
975
+ __m256i masked_bias_i8x32 = _mm256_and_si256(_mm256_set1_epi8((char)0x80), stride_mask_i8x32);
976
+ __m256i zero_i8x32 = _mm256_setzero_si256();
977
+ __m256i sum_u64x4 = _mm256_setzero_si256();
978
+ __m256i sumsq_low_i32x8 = _mm256_setzero_si256();
979
+ __m256i sumsq_high_i32x8 = _mm256_setzero_si256();
980
+ nk_size_t idx_scalars = 0;
981
+ nk_size_t total_scalars = count * stride_elements;
982
+ nk_size_t vector_element_count = 0;
983
+ nk_size_t step = elements_per_vector * stride_elements;
984
+ for (; idx_scalars + stride_elements + 31 <= total_scalars; idx_scalars += step) {
985
+ __m256i data_i8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx_scalars));
986
+ data_i8x32 = _mm256_and_si256(data_i8x32, stride_mask_i8x32);
987
+ __m256i unsigned_u8x32 = _mm256_xor_si256(data_i8x32, masked_bias_i8x32);
988
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(unsigned_u8x32, zero_i8x32));
989
+ __m256i low_i16x16 = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(data_i8x32));
990
+ __m256i high_i16x16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(data_i8x32, 1));
991
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, _mm256_madd_epi16(low_i16x16, low_i16x16));
992
+ sumsq_high_i32x8 = _mm256_add_epi32(sumsq_high_i32x8, _mm256_madd_epi16(high_i16x16, high_i16x16));
993
+ vector_element_count += elements_per_vector;
994
+ }
995
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, sumsq_high_i32x8);
996
+ __m256i sumsq_i64x4 = _mm256_cvtepi32_epi64(_mm256_castsi256_si128(sumsq_low_i32x8));
997
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_extracti128_si256(sumsq_low_i32x8, 1)));
998
+ nk_i64_t sum = (nk_i64_t)(nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4);
999
+ sum -= (nk_i64_t)128 * (nk_i64_t)vector_element_count;
1000
+ nk_u64_t sumsq = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_i64x4);
1001
+ nk_i8_t const *ptr = data_ptr + idx_scalars;
1002
+ nk_size_t remaining = count - idx_scalars / stride_elements;
1003
+ for (nk_size_t i = 0; i < remaining; ++i, ptr += stride_elements) {
1004
+ nk_i64_t val = (nk_i64_t)*ptr;
1005
+ sum += val, sumsq += (nk_u64_t)(val * val);
1006
+ }
1007
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
1008
+ }
1009
+
1010
+ NK_PUBLIC void nk_reduce_moments_i8_haswell( //
1011
+ nk_i8_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1012
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1013
+
1014
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i8_t);
1015
+ int aligned = (stride_bytes % sizeof(nk_i8_t) == 0);
1016
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
1017
+ else if (!aligned) nk_reduce_moments_i8_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1018
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 32) {
1019
+ nk_size_t left_count = count / 2;
1020
+ nk_i64_t left_sum, right_sum;
1021
+ nk_u64_t left_sumsq, right_sumsq;
1022
+ nk_reduce_moments_i8_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
1023
+ nk_reduce_moments_i8_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1024
+ &right_sum, &right_sumsq);
1025
+ *sum_ptr = nk_i64_saturating_add_serial(left_sum, right_sum);
1026
+ *sumsq_ptr = nk_u64_saturating_add_serial(left_sumsq, right_sumsq);
1027
+ }
1028
+ else if (stride_elements == 1) nk_reduce_moments_i8_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
1029
+ else if (stride_elements <= 8)
1030
+ nk_reduce_moments_i8_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
1031
+ else nk_reduce_moments_i8_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1032
+ }
1033
+
1034
+ NK_INTERNAL void nk_reduce_minmax_i8_haswell_contiguous_( //
1035
+ nk_i8_t const *data_ptr, nk_size_t count, //
1036
+ nk_i8_t *min_value_ptr, nk_size_t *min_index_ptr, //
1037
+ nk_i8_t *max_value_ptr, nk_size_t *max_index_ptr) {
1038
+
1039
+ __m256i min_i8x32 = _mm256_set1_epi8((char)NK_I8_MAX);
1040
+ __m256i max_i8x32 = _mm256_set1_epi8(NK_I8_MIN);
1041
+ __m256i min_loop_cycle_u8x32 = _mm256_setzero_si256();
1042
+ __m256i max_loop_cycle_u8x32 = _mm256_setzero_si256();
1043
+ __m256i current_loop_cycle_u8x32 = _mm256_setzero_si256();
1044
+ __m256i one_u8x32 = _mm256_set1_epi8(1);
1045
+
1046
+ nk_size_t idx = 0;
1047
+ for (; idx + 32 <= count; idx += 32) {
1048
+ __m256i data_i8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
1049
+ __m256i less_b8x32 = _mm256_cmpgt_epi8(min_i8x32, data_i8x32);
1050
+ __m256i greater_b8x32 = _mm256_cmpgt_epi8(data_i8x32, max_i8x32);
1051
+ min_i8x32 = _mm256_blendv_epi8(min_i8x32, data_i8x32, less_b8x32);
1052
+ max_i8x32 = _mm256_blendv_epi8(max_i8x32, data_i8x32, greater_b8x32);
1053
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, less_b8x32);
1054
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, greater_b8x32);
1055
+ current_loop_cycle_u8x32 = _mm256_add_epi8(current_loop_cycle_u8x32, one_u8x32);
1056
+ }
1057
+
1058
+ nk_size_t remaining = count - idx;
1059
+ if (remaining > 0) {
1060
+ nk_b256_vec_t tail_vec;
1061
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
1062
+ __m256i data_i8x32 = tail_vec.ymm;
1063
+ // Build lane mask and fill invalid lanes with identity values
1064
+ __m256i lane_indices_u8x32 = _mm256_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
1065
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
1066
+ __m256i valid_b8x32 = _mm256_cmpgt_epi8(_mm256_set1_epi8((char)remaining), lane_indices_u8x32);
1067
+ data_i8x32 = _mm256_blendv_epi8(_mm256_set1_epi8(NK_I8_MAX), data_i8x32, valid_b8x32);
1068
+ __m256i data_max_i8x32 = _mm256_blendv_epi8(_mm256_set1_epi8(NK_I8_MIN), tail_vec.ymm, valid_b8x32);
1069
+ __m256i less_b8x32 = _mm256_cmpgt_epi8(min_i8x32, data_i8x32);
1070
+ __m256i greater_b8x32 = _mm256_cmpgt_epi8(data_max_i8x32, max_i8x32);
1071
+ min_i8x32 = _mm256_blendv_epi8(min_i8x32, data_i8x32, less_b8x32);
1072
+ max_i8x32 = _mm256_blendv_epi8(max_i8x32, data_max_i8x32, greater_b8x32);
1073
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, less_b8x32);
1074
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, greater_b8x32);
1075
+ }
1076
+
1077
+ nk_i8_t min_value = nk_reduce_min_i8x32_haswell_(min_i8x32);
1078
+ nk_i8_t max_value = nk_reduce_max_i8x32_haswell_(max_i8x32);
1079
+ unsigned int min_lane, max_lane;
1080
+ {
1081
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(min_i8x32, _mm256_set1_epi8(min_value));
1082
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), min_loop_cycle_u8x32,
1083
+ value_match_b8x32);
1084
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
1085
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
1086
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
1087
+ }
1088
+ {
1089
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(max_i8x32, _mm256_set1_epi8(max_value));
1090
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), max_loop_cycle_u8x32,
1091
+ value_match_b8x32);
1092
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
1093
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
1094
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
1095
+ }
1096
+ nk_b256_vec_t loop_cycle_vec;
1097
+ loop_cycle_vec.ymm = min_loop_cycle_u8x32;
1098
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u8s[min_lane] * 32 + min_lane;
1099
+ loop_cycle_vec.ymm = max_loop_cycle_u8x32;
1100
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u8s[max_lane] * 32 + max_lane;
1101
+ }
1102
+
1103
+ NK_PUBLIC void nk_reduce_minmax_i8_haswell( //
1104
+ nk_i8_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1105
+ nk_i8_t *min_value_ptr, nk_size_t *min_index_ptr, //
1106
+ nk_i8_t *max_value_ptr, nk_size_t *max_index_ptr) {
1107
+
1108
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i8_t);
1109
+ int aligned = (stride_bytes % sizeof(nk_i8_t) == 0);
1110
+ if (count == 0)
1111
+ *min_value_ptr = NK_I8_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_I8_MIN,
1112
+ *max_index_ptr = NK_SIZE_MAX;
1113
+ else if (!aligned)
1114
+ nk_reduce_minmax_i8_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1115
+ max_index_ptr);
1116
+ else if (count > (nk_size_t)(NK_U8_MAX + 1) * 32) {
1117
+ nk_size_t left_count = count / 2;
1118
+ nk_i8_t left_min, right_min, left_max, right_max;
1119
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
1120
+ nk_reduce_minmax_i8_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
1121
+ &left_max_index);
1122
+ nk_reduce_minmax_i8_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1123
+ &right_min, &right_min_index, &right_max, &right_max_index);
1124
+ if (right_min < left_min) *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
1125
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
1126
+ if (right_max > left_max) *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
1127
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
1128
+ }
1129
+ else if (stride_elements == 1)
1130
+ nk_reduce_minmax_i8_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
1131
+ max_index_ptr);
1132
+ else
1133
+ nk_reduce_minmax_i8_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1134
+ max_index_ptr);
1135
+ }
1136
+
1137
+ NK_INTERNAL void nk_reduce_moments_u8_haswell_contiguous_( //
1138
+ nk_u8_t const *data_ptr, nk_size_t count, //
1139
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1140
+
1141
+ __m256i zero_u8x32 = _mm256_setzero_si256();
1142
+ __m256i sum_u64x4 = _mm256_setzero_si256();
1143
+ __m256i sumsq_low_i32x8 = _mm256_setzero_si256();
1144
+ __m256i sumsq_high_i32x8 = _mm256_setzero_si256();
1145
+ nk_size_t idx = 0;
1146
+ for (; idx + 32 <= count; idx += 32) {
1147
+ __m256i data_u8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
1148
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(data_u8x32, zero_u8x32));
1149
+ __m256i low_i16x16 = _mm256_cvtepu8_epi16(_mm256_castsi256_si128(data_u8x32));
1150
+ __m256i high_i16x16 = _mm256_cvtepu8_epi16(_mm256_extracti128_si256(data_u8x32, 1));
1151
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, _mm256_madd_epi16(low_i16x16, low_i16x16));
1152
+ sumsq_high_i32x8 = _mm256_add_epi32(sumsq_high_i32x8, _mm256_madd_epi16(high_i16x16, high_i16x16));
1153
+ }
1154
+ nk_size_t remaining = count - idx;
1155
+ if (remaining > 0) {
1156
+ nk_b256_vec_t tail_vec;
1157
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
1158
+ __m256i data_u8x32 = tail_vec.ymm;
1159
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(data_u8x32, zero_u8x32));
1160
+ __m256i low_i16x16 = _mm256_cvtepu8_epi16(_mm256_castsi256_si128(data_u8x32));
1161
+ __m256i high_i16x16 = _mm256_cvtepu8_epi16(_mm256_extracti128_si256(data_u8x32, 1));
1162
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, _mm256_madd_epi16(low_i16x16, low_i16x16));
1163
+ sumsq_high_i32x8 = _mm256_add_epi32(sumsq_high_i32x8, _mm256_madd_epi16(high_i16x16, high_i16x16));
1164
+ }
1165
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, sumsq_high_i32x8);
1166
+ __m256i sumsq_u64x4 = _mm256_cvtepu32_epi64(_mm256_castsi256_si128(sumsq_low_i32x8));
1167
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(sumsq_low_i32x8, 1)));
1168
+ *sum_ptr = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4),
1169
+ *sumsq_ptr = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_u64x4);
1170
+ }
1171
+
1172
+ NK_INTERNAL void nk_reduce_moments_u8_haswell_strided_( //
1173
+ nk_u8_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
1174
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1175
+
1176
+ __m256i stride_mask_u8x32 = nk_stride_blend_u1x32_(stride_elements);
1177
+ __m256i zero_u8x32 = _mm256_setzero_si256();
1178
+ __m256i sum_u64x4 = _mm256_setzero_si256();
1179
+ __m256i sumsq_low_i32x8 = _mm256_setzero_si256();
1180
+ __m256i sumsq_high_i32x8 = _mm256_setzero_si256();
1181
+ nk_size_t idx_scalars = 0;
1182
+ nk_size_t total_scalars = count * stride_elements;
1183
+ nk_size_t step = nk_size_round_up_to_multiple_(32, stride_elements);
1184
+ for (; idx_scalars + stride_elements + 31 <= total_scalars; idx_scalars += step) {
1185
+ __m256i data_u8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx_scalars));
1186
+ data_u8x32 = _mm256_and_si256(data_u8x32, stride_mask_u8x32);
1187
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(data_u8x32, zero_u8x32));
1188
+ __m256i low_i16x16 = _mm256_cvtepu8_epi16(_mm256_castsi256_si128(data_u8x32));
1189
+ __m256i high_i16x16 = _mm256_cvtepu8_epi16(_mm256_extracti128_si256(data_u8x32, 1));
1190
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, _mm256_madd_epi16(low_i16x16, low_i16x16));
1191
+ sumsq_high_i32x8 = _mm256_add_epi32(sumsq_high_i32x8, _mm256_madd_epi16(high_i16x16, high_i16x16));
1192
+ }
1193
+ sumsq_low_i32x8 = _mm256_add_epi32(sumsq_low_i32x8, sumsq_high_i32x8);
1194
+ __m256i sumsq_u64x4 = _mm256_cvtepu32_epi64(_mm256_castsi256_si128(sumsq_low_i32x8));
1195
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(sumsq_low_i32x8, 1)));
1196
+ nk_u64_t sum = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4);
1197
+ nk_u64_t sumsq = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_u64x4);
1198
+ nk_u8_t const *ptr = data_ptr + idx_scalars;
1199
+ nk_size_t remaining_elements = count - idx_scalars / stride_elements;
1200
+ for (nk_size_t i = 0; i < remaining_elements; ++i, ptr += stride_elements) {
1201
+ nk_u64_t val = (nk_u64_t)*ptr;
1202
+ sum += val, sumsq += val * val;
1203
+ }
1204
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
1205
+ }
1206
+
1207
+ NK_PUBLIC void nk_reduce_moments_u8_haswell( //
1208
+ nk_u8_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1209
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1210
+
1211
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u8_t);
1212
+ int aligned = (stride_bytes % sizeof(nk_u8_t) == 0);
1213
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
1214
+ else if (!aligned) nk_reduce_moments_u8_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1215
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 32) {
1216
+ nk_size_t left_count = count / 2;
1217
+ nk_u64_t left_sum, left_sumsq, right_sum, right_sumsq;
1218
+ nk_reduce_moments_u8_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
1219
+ nk_reduce_moments_u8_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1220
+ &right_sum, &right_sumsq);
1221
+ *sum_ptr = nk_u64_saturating_add_serial(left_sum, right_sum);
1222
+ *sumsq_ptr = nk_u64_saturating_add_serial(left_sumsq, right_sumsq);
1223
+ }
1224
+ else if (stride_elements == 1) nk_reduce_moments_u8_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
1225
+ else if (stride_elements <= 8)
1226
+ nk_reduce_moments_u8_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
1227
+ else nk_reduce_moments_u8_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1228
+ }
1229
+
1230
+ NK_INTERNAL void nk_reduce_minmax_u8_haswell_contiguous_( //
1231
+ nk_u8_t const *data_ptr, nk_size_t count, //
1232
+ nk_u8_t *min_value_ptr, nk_size_t *min_index_ptr, //
1233
+ nk_u8_t *max_value_ptr, nk_size_t *max_index_ptr) {
1234
+
1235
+ // XOR-bias to signed domain for _mm256_cmpgt_epi8
1236
+ __m256i bias_u8x32 = _mm256_set1_epi8((char)0x80);
1237
+ __m256i min_biased_i8x32 = _mm256_set1_epi8((char)NK_I8_MAX);
1238
+ __m256i max_biased_i8x32 = _mm256_set1_epi8(NK_I8_MIN);
1239
+ __m256i min_loop_cycle_u8x32 = _mm256_setzero_si256();
1240
+ __m256i max_loop_cycle_u8x32 = _mm256_setzero_si256();
1241
+ __m256i current_loop_cycle_u8x32 = _mm256_setzero_si256();
1242
+ __m256i one_u8x32 = _mm256_set1_epi8(1);
1243
+
1244
+ nk_size_t idx = 0;
1245
+ for (; idx + 32 <= count; idx += 32) {
1246
+ __m256i data_biased_i8x32 = _mm256_xor_si256(_mm256_loadu_si256((__m256i const *)(data_ptr + idx)), bias_u8x32);
1247
+ __m256i less_b8x32 = _mm256_cmpgt_epi8(min_biased_i8x32, data_biased_i8x32);
1248
+ __m256i greater_b8x32 = _mm256_cmpgt_epi8(data_biased_i8x32, max_biased_i8x32);
1249
+ min_biased_i8x32 = _mm256_blendv_epi8(min_biased_i8x32, data_biased_i8x32, less_b8x32);
1250
+ max_biased_i8x32 = _mm256_blendv_epi8(max_biased_i8x32, data_biased_i8x32, greater_b8x32);
1251
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, less_b8x32);
1252
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, greater_b8x32);
1253
+ current_loop_cycle_u8x32 = _mm256_add_epi8(current_loop_cycle_u8x32, one_u8x32);
1254
+ }
1255
+
1256
+ nk_size_t remaining = count - idx;
1257
+ if (remaining > 0) {
1258
+ nk_b256_vec_t tail_vec;
1259
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
1260
+ __m256i data_biased_i8x32 = _mm256_xor_si256(tail_vec.ymm, bias_u8x32);
1261
+ __m256i lane_indices_u8x32 = _mm256_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
1262
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
1263
+ __m256i valid_b8x32 = _mm256_cmpgt_epi8(_mm256_set1_epi8((char)remaining), lane_indices_u8x32);
1264
+ // Biased identity: NK_U8_MAX ^ 0x80 = 0x7F for min, NK_U8_MIN ^ 0x80 = 0x80 for max
1265
+ __m256i data_min_i8x32 = _mm256_blendv_epi8(_mm256_set1_epi8(0x7F), data_biased_i8x32, valid_b8x32);
1266
+ __m256i data_max_i8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)0x80), data_biased_i8x32, valid_b8x32);
1267
+ __m256i less_b8x32 = _mm256_cmpgt_epi8(min_biased_i8x32, data_min_i8x32);
1268
+ __m256i greater_b8x32 = _mm256_cmpgt_epi8(data_max_i8x32, max_biased_i8x32);
1269
+ min_biased_i8x32 = _mm256_blendv_epi8(min_biased_i8x32, data_min_i8x32, less_b8x32);
1270
+ max_biased_i8x32 = _mm256_blendv_epi8(max_biased_i8x32, data_max_i8x32, greater_b8x32);
1271
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, less_b8x32);
1272
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, greater_b8x32);
1273
+ }
1274
+
1275
+ // Un-bias to get real u8 values
1276
+ __m256i min_u8x32 = _mm256_xor_si256(min_biased_i8x32, bias_u8x32);
1277
+ __m256i max_u8x32 = _mm256_xor_si256(max_biased_i8x32, bias_u8x32);
1278
+ nk_u8_t min_value = nk_reduce_min_u8x32_haswell_(min_u8x32);
1279
+ nk_u8_t max_value = nk_reduce_max_u8x32_haswell_(max_u8x32);
1280
+ unsigned int min_lane, max_lane;
1281
+ {
1282
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(min_u8x32, _mm256_set1_epi8((char)min_value));
1283
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), min_loop_cycle_u8x32,
1284
+ value_match_b8x32);
1285
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
1286
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
1287
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
1288
+ }
1289
+ {
1290
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(max_u8x32, _mm256_set1_epi8((char)max_value));
1291
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), max_loop_cycle_u8x32,
1292
+ value_match_b8x32);
1293
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
1294
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
1295
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
1296
+ }
1297
+ nk_b256_vec_t loop_cycle_vec;
1298
+ loop_cycle_vec.ymm = min_loop_cycle_u8x32;
1299
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u8s[min_lane] * 32 + min_lane;
1300
+ loop_cycle_vec.ymm = max_loop_cycle_u8x32;
1301
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u8s[max_lane] * 32 + max_lane;
1302
+ }
1303
+
1304
+ NK_PUBLIC void nk_reduce_minmax_u8_haswell( //
1305
+ nk_u8_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1306
+ nk_u8_t *min_value_ptr, nk_size_t *min_index_ptr, //
1307
+ nk_u8_t *max_value_ptr, nk_size_t *max_index_ptr) {
1308
+
1309
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u8_t);
1310
+ int aligned = (stride_bytes % sizeof(nk_u8_t) == 0);
1311
+ if (count == 0)
1312
+ *min_value_ptr = NK_U8_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = 0, *max_index_ptr = NK_SIZE_MAX;
1313
+ else if (!aligned)
1314
+ nk_reduce_minmax_u8_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1315
+ max_index_ptr);
1316
+ else if (count > (nk_size_t)(NK_U8_MAX + 1) * 32) {
1317
+ nk_size_t left_count = count / 2;
1318
+ nk_u8_t left_min, right_min, left_max, right_max;
1319
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
1320
+ nk_reduce_minmax_u8_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
1321
+ &left_max_index);
1322
+ nk_reduce_minmax_u8_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1323
+ &right_min, &right_min_index, &right_max, &right_max_index);
1324
+ if (right_min < left_min) *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
1325
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
1326
+ if (right_max > left_max) *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
1327
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
1328
+ }
1329
+ else if (stride_elements == 1)
1330
+ nk_reduce_minmax_u8_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
1331
+ max_index_ptr);
1332
+ else
1333
+ nk_reduce_minmax_u8_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1334
+ max_index_ptr);
1335
+ }
1336
+
1337
+ NK_INTERNAL void nk_reduce_moments_i16_haswell_contiguous_( //
1338
+ nk_i16_t const *data_ptr, nk_size_t count, //
1339
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1340
+
1341
+ __m256i ones_i16x16 = _mm256_set1_epi16(1);
1342
+ __m256i sum_i32x8 = _mm256_setzero_si256();
1343
+ __m256i sumsq_i64x4 = _mm256_setzero_si256();
1344
+ nk_size_t idx = 0;
1345
+ for (; idx + 16 <= count; idx += 16) {
1346
+ __m256i data_i16x16 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
1347
+ sum_i32x8 = _mm256_add_epi32(sum_i32x8, _mm256_madd_epi16(data_i16x16, ones_i16x16));
1348
+ __m256i sq_i32x8 = _mm256_madd_epi16(data_i16x16, data_i16x16);
1349
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_castsi256_si128(sq_i32x8)));
1350
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_extracti128_si256(sq_i32x8, 1)));
1351
+ }
1352
+ nk_size_t remaining = count - idx;
1353
+ if (remaining > 0) {
1354
+ nk_b256_vec_t tail_vec;
1355
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &tail_vec, remaining);
1356
+ __m256i data_i16x16 = tail_vec.ymm;
1357
+ sum_i32x8 = _mm256_add_epi32(sum_i32x8, _mm256_madd_epi16(data_i16x16, ones_i16x16));
1358
+ __m256i sq_i32x8 = _mm256_madd_epi16(data_i16x16, data_i16x16);
1359
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_castsi256_si128(sq_i32x8)));
1360
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_extracti128_si256(sq_i32x8, 1)));
1361
+ }
1362
+ __m256i sum_i64x4 = _mm256_add_epi64( //
1363
+ _mm256_cvtepi32_epi64(_mm256_castsi256_si128(sum_i32x8)), //
1364
+ _mm256_cvtepi32_epi64(_mm256_extracti128_si256(sum_i32x8, 1))); //
1365
+ *sum_ptr = nk_reduce_add_i64x4_haswell_(sum_i64x4),
1366
+ *sumsq_ptr = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_i64x4);
1367
+ }
1368
+
1369
+ NK_INTERNAL void nk_reduce_moments_i16_haswell_strided_( //
1370
+ nk_i16_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
1371
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1372
+
1373
+ __m256i stride_mask_i16x16 = nk_stride_blend_b16x16_(stride_elements);
1374
+ __m256i ones_i16x16 = _mm256_set1_epi16(1);
1375
+ __m256i sum_i32x8 = _mm256_setzero_si256();
1376
+ __m256i sumsq_i64x4 = _mm256_setzero_si256();
1377
+ nk_size_t idx_scalars = 0;
1378
+ nk_size_t total_scalars = count * stride_elements;
1379
+ nk_size_t step = nk_size_round_up_to_multiple_(16, stride_elements);
1380
+ for (; idx_scalars + stride_elements + 15 <= total_scalars; idx_scalars += step) {
1381
+ __m256i data_i16x16 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx_scalars));
1382
+ data_i16x16 = _mm256_and_si256(data_i16x16, stride_mask_i16x16);
1383
+ sum_i32x8 = _mm256_add_epi32(sum_i32x8, _mm256_madd_epi16(data_i16x16, ones_i16x16));
1384
+ __m256i sq_i32x8 = _mm256_madd_epi16(data_i16x16, data_i16x16);
1385
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_castsi256_si128(sq_i32x8)));
1386
+ sumsq_i64x4 = _mm256_add_epi64(sumsq_i64x4, _mm256_cvtepi32_epi64(_mm256_extracti128_si256(sq_i32x8, 1)));
1387
+ }
1388
+ __m256i sum_i64x4 = _mm256_add_epi64( //
1389
+ _mm256_cvtepi32_epi64(_mm256_castsi256_si128(sum_i32x8)), //
1390
+ _mm256_cvtepi32_epi64(_mm256_extracti128_si256(sum_i32x8, 1))); //
1391
+ nk_i64_t sum = nk_reduce_add_i64x4_haswell_(sum_i64x4);
1392
+ nk_u64_t sumsq = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_i64x4);
1393
+ nk_i16_t const *ptr = data_ptr + idx_scalars;
1394
+ nk_size_t remaining = count - idx_scalars / stride_elements;
1395
+ for (nk_size_t i = 0; i < remaining; ++i, ptr += stride_elements) {
1396
+ nk_i64_t val = (nk_i64_t)*ptr;
1397
+ sum += val, sumsq += (nk_u64_t)(val * val);
1398
+ }
1399
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
1400
+ }
1401
+
1402
+ NK_PUBLIC void nk_reduce_moments_i16_haswell( //
1403
+ nk_i16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1404
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1405
+
1406
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i16_t);
1407
+ int aligned = (stride_bytes % sizeof(nk_i16_t) == 0);
1408
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
1409
+ else if (!aligned) nk_reduce_moments_i16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1410
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 16) {
1411
+ nk_size_t left_count = count / 2;
1412
+ nk_i64_t left_sum, right_sum;
1413
+ nk_u64_t left_sumsq, right_sumsq;
1414
+ nk_reduce_moments_i16_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
1415
+ nk_reduce_moments_i16_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1416
+ &right_sum, &right_sumsq);
1417
+ *sum_ptr = nk_i64_saturating_add_serial(left_sum, right_sum);
1418
+ *sumsq_ptr = nk_u64_saturating_add_serial(left_sumsq, right_sumsq);
1419
+ }
1420
+ else if (stride_elements == 1) nk_reduce_moments_i16_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
1421
+ else if (stride_elements <= 8)
1422
+ nk_reduce_moments_i16_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
1423
+ else nk_reduce_moments_i16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1424
+ }
1425
+
1426
+ NK_INTERNAL void nk_reduce_minmax_i16_haswell_contiguous_( //
1427
+ nk_i16_t const *data_ptr, nk_size_t count, //
1428
+ nk_i16_t *min_value_ptr, nk_size_t *min_index_ptr, //
1429
+ nk_i16_t *max_value_ptr, nk_size_t *max_index_ptr) {
1430
+
1431
+ __m256i min_i16x16 = _mm256_set1_epi16((short)NK_I16_MAX);
1432
+ __m256i max_i16x16 = _mm256_set1_epi16(NK_I16_MIN);
1433
+ __m256i min_loop_cycle_u16x16 = _mm256_setzero_si256();
1434
+ __m256i max_loop_cycle_u16x16 = _mm256_setzero_si256();
1435
+ __m256i current_loop_cycle_u16x16 = _mm256_setzero_si256();
1436
+ __m256i one_u16x16 = _mm256_set1_epi16(1);
1437
+
1438
+ nk_size_t idx = 0;
1439
+ for (; idx + 16 <= count; idx += 16) {
1440
+ __m256i data_i16x16 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
1441
+ __m256i less_b16x16 = _mm256_cmpgt_epi16(min_i16x16, data_i16x16);
1442
+ __m256i greater_b16x16 = _mm256_cmpgt_epi16(data_i16x16, max_i16x16);
1443
+ min_i16x16 = _mm256_blendv_epi8(min_i16x16, data_i16x16, less_b16x16);
1444
+ max_i16x16 = _mm256_blendv_epi8(max_i16x16, data_i16x16, greater_b16x16);
1445
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16, less_b16x16);
1446
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16, greater_b16x16);
1447
+ current_loop_cycle_u16x16 = _mm256_add_epi16(current_loop_cycle_u16x16, one_u16x16);
1448
+ }
1449
+
1450
+ nk_size_t remaining = count - idx;
1451
+ if (remaining > 0) {
1452
+ nk_b256_vec_t tail_vec;
1453
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &tail_vec, remaining);
1454
+ // Build 16-bit lane mask
1455
+ __m256i lane_indices_u16x16 = _mm256_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
1456
+ __m256i valid_b16x16 = _mm256_cmpgt_epi16(_mm256_set1_epi16((short)remaining), lane_indices_u16x16);
1457
+ __m256i data_min_i16x16 = _mm256_blendv_epi8(_mm256_set1_epi16(NK_I16_MAX), tail_vec.ymm, valid_b16x16);
1458
+ __m256i data_max_i16x16 = _mm256_blendv_epi8(_mm256_set1_epi16(NK_I16_MIN), tail_vec.ymm, valid_b16x16);
1459
+ __m256i less_b16x16 = _mm256_cmpgt_epi16(min_i16x16, data_min_i16x16);
1460
+ __m256i greater_b16x16 = _mm256_cmpgt_epi16(data_max_i16x16, max_i16x16);
1461
+ min_i16x16 = _mm256_blendv_epi8(min_i16x16, data_min_i16x16, less_b16x16);
1462
+ max_i16x16 = _mm256_blendv_epi8(max_i16x16, data_max_i16x16, greater_b16x16);
1463
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16, less_b16x16);
1464
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16, greater_b16x16);
1465
+ }
1466
+
1467
+ nk_i16_t min_value = nk_reduce_min_i16x16_haswell_(min_i16x16);
1468
+ nk_i16_t max_value = nk_reduce_max_i16x16_haswell_(max_i16x16);
1469
+ unsigned int min_lane, max_lane;
1470
+ {
1471
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(min_i16x16, _mm256_set1_epi16(min_value));
1472
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), min_loop_cycle_u16x16,
1473
+ value_match_b16x16);
1474
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
1475
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
1476
+ _mm256_set1_epi16((short)earliest_loop_cycle));
1477
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
1478
+ }
1479
+ {
1480
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(max_i16x16, _mm256_set1_epi16(max_value));
1481
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), max_loop_cycle_u16x16,
1482
+ value_match_b16x16);
1483
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
1484
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
1485
+ _mm256_set1_epi16((short)earliest_loop_cycle));
1486
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
1487
+ }
1488
+ nk_b256_vec_t loop_cycle_vec;
1489
+ loop_cycle_vec.ymm = min_loop_cycle_u16x16;
1490
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u16s[min_lane] * 16 + min_lane;
1491
+ loop_cycle_vec.ymm = max_loop_cycle_u16x16;
1492
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u16s[max_lane] * 16 + max_lane;
1493
+ }
1494
+
1495
+ NK_PUBLIC void nk_reduce_minmax_i16_haswell( //
1496
+ nk_i16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1497
+ nk_i16_t *min_value_ptr, nk_size_t *min_index_ptr, //
1498
+ nk_i16_t *max_value_ptr, nk_size_t *max_index_ptr) {
1499
+
1500
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i16_t);
1501
+ int aligned = (stride_bytes % sizeof(nk_i16_t) == 0);
1502
+ if (count == 0)
1503
+ *min_value_ptr = NK_I16_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_I16_MIN,
1504
+ *max_index_ptr = NK_SIZE_MAX;
1505
+ else if (!aligned)
1506
+ nk_reduce_minmax_i16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1507
+ max_index_ptr);
1508
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 16) {
1509
+ nk_size_t left_count = count / 2;
1510
+ nk_i16_t left_min, right_min, left_max, right_max;
1511
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
1512
+ nk_reduce_minmax_i16_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
1513
+ &left_max_index);
1514
+ nk_reduce_minmax_i16_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1515
+ &right_min, &right_min_index, &right_max, &right_max_index);
1516
+ if (right_min < left_min) *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
1517
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
1518
+ if (right_max > left_max) *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
1519
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
1520
+ }
1521
+ else if (stride_elements == 1)
1522
+ nk_reduce_minmax_i16_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
1523
+ max_index_ptr);
1524
+ else
1525
+ nk_reduce_minmax_i16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1526
+ max_index_ptr);
1527
+ }
1528
+
1529
+ NK_INTERNAL void nk_reduce_moments_u16_haswell_contiguous_( //
1530
+ nk_u16_t const *data_ptr, nk_size_t count, //
1531
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1532
+
1533
+ // Widen u16→u32, square in u32, widen to u64.
1534
+ __m256i sum_u32x8 = _mm256_setzero_si256();
1535
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
1536
+ nk_size_t idx = 0;
1537
+ for (; idx + 8 <= count; idx += 8) {
1538
+ __m256i data_u32x8 = _mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i const *)(data_ptr + idx)));
1539
+ sum_u32x8 = _mm256_add_epi32(sum_u32x8, data_u32x8);
1540
+ __m256i sq_u32x8 = _mm256_mullo_epi32(data_u32x8, data_u32x8);
1541
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_castsi256_si128(sq_u32x8)));
1542
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(sq_u32x8, 1)));
1543
+ }
1544
+ nk_size_t remaining = count - idx;
1545
+ if (remaining > 0) {
1546
+ nk_b256_vec_t tail_vec;
1547
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &tail_vec, remaining);
1548
+ __m256i data_u32x8 = _mm256_cvtepu16_epi32(_mm256_castsi256_si128(tail_vec.ymm));
1549
+ sum_u32x8 = _mm256_add_epi32(sum_u32x8, data_u32x8);
1550
+ __m256i sq_u32x8 = _mm256_mullo_epi32(data_u32x8, data_u32x8);
1551
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_castsi256_si128(sq_u32x8)));
1552
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(sq_u32x8, 1)));
1553
+ }
1554
+ __m256i sum_u64x4 = _mm256_add_epi64( //
1555
+ _mm256_cvtepu32_epi64(_mm256_castsi256_si128(sum_u32x8)), //
1556
+ _mm256_cvtepu32_epi64(_mm256_extracti128_si256(sum_u32x8, 1))); //
1557
+ *sum_ptr = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4),
1558
+ *sumsq_ptr = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_u64x4);
1559
+ }
1560
+
1561
+ NK_INTERNAL void nk_reduce_moments_u16_haswell_strided_( //
1562
+ nk_u16_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
1563
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1564
+
1565
+ __m256i stride_mask_i16x16 = nk_stride_blend_b16x16_(stride_elements);
1566
+ __m256i sum_u32x8 = _mm256_setzero_si256();
1567
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
1568
+ nk_size_t idx_scalars = 0;
1569
+ nk_size_t total_scalars = count * stride_elements;
1570
+ nk_size_t step = nk_size_round_up_to_multiple_(16, stride_elements);
1571
+ for (; idx_scalars + stride_elements + 15 <= total_scalars; idx_scalars += step) {
1572
+ __m256i data_u16x16 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx_scalars));
1573
+ data_u16x16 = _mm256_and_si256(data_u16x16, stride_mask_i16x16);
1574
+ __m256i low_u32x8 = _mm256_cvtepu16_epi32(_mm256_castsi256_si128(data_u16x16));
1575
+ __m256i high_u32x8 = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(data_u16x16, 1));
1576
+ sum_u32x8 = _mm256_add_epi32(sum_u32x8, low_u32x8);
1577
+ sum_u32x8 = _mm256_add_epi32(sum_u32x8, high_u32x8);
1578
+ __m256i low_sq_u32x8 = _mm256_mullo_epi32(low_u32x8, low_u32x8);
1579
+ __m256i high_sq_u32x8 = _mm256_mullo_epi32(high_u32x8, high_u32x8);
1580
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_castsi256_si128(low_sq_u32x8)));
1581
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(low_sq_u32x8, 1)));
1582
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_castsi256_si128(high_sq_u32x8)));
1583
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(high_sq_u32x8, 1)));
1584
+ }
1585
+ __m256i sum_u64x4 = _mm256_add_epi64( //
1586
+ _mm256_cvtepu32_epi64(_mm256_castsi256_si128(sum_u32x8)), //
1587
+ _mm256_cvtepu32_epi64(_mm256_extracti128_si256(sum_u32x8, 1))); //
1588
+ nk_u64_t sum = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4);
1589
+ nk_u64_t sumsq = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_u64x4);
1590
+ nk_u16_t const *ptr = data_ptr + idx_scalars;
1591
+ nk_size_t remaining = count - idx_scalars / stride_elements;
1592
+ for (nk_size_t i = 0; i < remaining; ++i, ptr += stride_elements) {
1593
+ nk_u64_t val = (nk_u64_t)*ptr;
1594
+ sum += val, sumsq += val * val;
1595
+ }
1596
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
1597
+ }
1598
+
1599
+ NK_PUBLIC void nk_reduce_moments_u16_haswell( //
1600
+ nk_u16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1601
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1602
+
1603
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u16_t);
1604
+ int aligned = (stride_bytes % sizeof(nk_u16_t) == 0);
1605
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
1606
+ else if (!aligned) nk_reduce_moments_u16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1607
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 8) {
1608
+ nk_size_t left_count = count / 2;
1609
+ nk_u64_t left_sum, left_sumsq, right_sum, right_sumsq;
1610
+ nk_reduce_moments_u16_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
1611
+ nk_reduce_moments_u16_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1612
+ &right_sum, &right_sumsq);
1613
+ *sum_ptr = nk_u64_saturating_add_serial(left_sum, right_sum);
1614
+ *sumsq_ptr = nk_u64_saturating_add_serial(left_sumsq, right_sumsq);
1615
+ }
1616
+ else if (stride_elements == 1) nk_reduce_moments_u16_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
1617
+ else if (stride_elements <= 8)
1618
+ nk_reduce_moments_u16_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
1619
+ else nk_reduce_moments_u16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1620
+ }
1621
+
1622
+ NK_INTERNAL void nk_reduce_minmax_u16_haswell_contiguous_( //
1623
+ nk_u16_t const *data_ptr, nk_size_t count, //
1624
+ nk_u16_t *min_value_ptr, nk_size_t *min_index_ptr, //
1625
+ nk_u16_t *max_value_ptr, nk_size_t *max_index_ptr) {
1626
+
1627
+ // XOR-bias to signed domain for _mm256_cmpgt_epi16
1628
+ __m256i bias_u16x16 = _mm256_set1_epi16((short)0x8000);
1629
+ __m256i min_biased_i16x16 = _mm256_set1_epi16((short)NK_I16_MAX);
1630
+ __m256i max_biased_i16x16 = _mm256_set1_epi16(NK_I16_MIN);
1631
+ __m256i min_loop_cycle_u16x16 = _mm256_setzero_si256();
1632
+ __m256i max_loop_cycle_u16x16 = _mm256_setzero_si256();
1633
+ __m256i current_loop_cycle_u16x16 = _mm256_setzero_si256();
1634
+ __m256i one_u16x16 = _mm256_set1_epi16(1);
1635
+
1636
+ nk_size_t idx = 0;
1637
+ for (; idx + 16 <= count; idx += 16) {
1638
+ __m256i data_biased_i16x16 = _mm256_xor_si256(_mm256_loadu_si256((__m256i const *)(data_ptr + idx)),
1639
+ bias_u16x16);
1640
+ __m256i less_b16x16 = _mm256_cmpgt_epi16(min_biased_i16x16, data_biased_i16x16);
1641
+ __m256i greater_b16x16 = _mm256_cmpgt_epi16(data_biased_i16x16, max_biased_i16x16);
1642
+ min_biased_i16x16 = _mm256_blendv_epi8(min_biased_i16x16, data_biased_i16x16, less_b16x16);
1643
+ max_biased_i16x16 = _mm256_blendv_epi8(max_biased_i16x16, data_biased_i16x16, greater_b16x16);
1644
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16, less_b16x16);
1645
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16, greater_b16x16);
1646
+ current_loop_cycle_u16x16 = _mm256_add_epi16(current_loop_cycle_u16x16, one_u16x16);
1647
+ }
1648
+
1649
+ nk_size_t remaining = count - idx;
1650
+ if (remaining > 0) {
1651
+ nk_b256_vec_t tail_vec;
1652
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &tail_vec, remaining);
1653
+ __m256i data_biased_i16x16 = _mm256_xor_si256(tail_vec.ymm, bias_u16x16);
1654
+ __m256i lane_indices_u16x16 = _mm256_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
1655
+ __m256i valid_b16x16 = _mm256_cmpgt_epi16(_mm256_set1_epi16((short)remaining), lane_indices_u16x16);
1656
+ // Biased identity: NK_U16_MAX ^ 0x8000 = 0x7FFF for min, 0 ^ 0x8000 = 0x8000 for max
1657
+ __m256i data_min_i16x16 = _mm256_blendv_epi8(_mm256_set1_epi16(0x7FFF), data_biased_i16x16, valid_b16x16);
1658
+ __m256i data_max_i16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)0x8000), data_biased_i16x16,
1659
+ valid_b16x16);
1660
+ __m256i less_b16x16 = _mm256_cmpgt_epi16(min_biased_i16x16, data_min_i16x16);
1661
+ __m256i greater_b16x16 = _mm256_cmpgt_epi16(data_max_i16x16, max_biased_i16x16);
1662
+ min_biased_i16x16 = _mm256_blendv_epi8(min_biased_i16x16, data_min_i16x16, less_b16x16);
1663
+ max_biased_i16x16 = _mm256_blendv_epi8(max_biased_i16x16, data_max_i16x16, greater_b16x16);
1664
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16, less_b16x16);
1665
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16, greater_b16x16);
1666
+ }
1667
+
1668
+ __m256i min_u16x16 = _mm256_xor_si256(min_biased_i16x16, bias_u16x16);
1669
+ __m256i max_u16x16 = _mm256_xor_si256(max_biased_i16x16, bias_u16x16);
1670
+ nk_u16_t min_value = nk_reduce_min_u16x16_haswell_(min_u16x16);
1671
+ nk_u16_t max_value = nk_reduce_max_u16x16_haswell_(max_u16x16);
1672
+ unsigned int min_lane, max_lane;
1673
+ {
1674
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(min_u16x16, _mm256_set1_epi16((short)min_value));
1675
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), min_loop_cycle_u16x16,
1676
+ value_match_b16x16);
1677
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
1678
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
1679
+ _mm256_set1_epi16((short)earliest_loop_cycle));
1680
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
1681
+ }
1682
+ {
1683
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(max_u16x16, _mm256_set1_epi16((short)max_value));
1684
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), max_loop_cycle_u16x16,
1685
+ value_match_b16x16);
1686
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
1687
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
1688
+ _mm256_set1_epi16((short)earliest_loop_cycle));
1689
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
1690
+ }
1691
+ nk_b256_vec_t loop_cycle_vec;
1692
+ loop_cycle_vec.ymm = min_loop_cycle_u16x16;
1693
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u16s[min_lane] * 16 + min_lane;
1694
+ loop_cycle_vec.ymm = max_loop_cycle_u16x16;
1695
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u16s[max_lane] * 16 + max_lane;
1696
+ }
1697
+
1698
+ NK_PUBLIC void nk_reduce_minmax_u16_haswell( //
1699
+ nk_u16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1700
+ nk_u16_t *min_value_ptr, nk_size_t *min_index_ptr, //
1701
+ nk_u16_t *max_value_ptr, nk_size_t *max_index_ptr) {
1702
+
1703
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u16_t);
1704
+ int aligned = (stride_bytes % sizeof(nk_u16_t) == 0);
1705
+ if (count == 0)
1706
+ *min_value_ptr = NK_U16_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = 0, *max_index_ptr = NK_SIZE_MAX;
1707
+ else if (!aligned)
1708
+ nk_reduce_minmax_u16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1709
+ max_index_ptr);
1710
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 16) {
1711
+ nk_size_t left_count = count / 2;
1712
+ nk_u16_t left_min, right_min, left_max, right_max;
1713
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
1714
+ nk_reduce_minmax_u16_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
1715
+ &left_max_index);
1716
+ nk_reduce_minmax_u16_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1717
+ &right_min, &right_min_index, &right_max, &right_max_index);
1718
+ if (right_min < left_min) *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
1719
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
1720
+ if (right_max > left_max) *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
1721
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
1722
+ }
1723
+ else if (stride_elements == 1)
1724
+ nk_reduce_minmax_u16_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
1725
+ max_index_ptr);
1726
+ else
1727
+ nk_reduce_minmax_u16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1728
+ max_index_ptr);
1729
+ }
1730
+
1731
+ NK_INTERNAL void nk_reduce_moments_i32_haswell_contiguous_( //
1732
+ nk_i32_t const *data_ptr, nk_size_t count, //
1733
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1734
+
1735
+ __m256i sum_low_i64x4 = _mm256_setzero_si256();
1736
+ __m256i sum_high_i64x4 = _mm256_setzero_si256();
1737
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
1738
+ int sumsq_overflow_mask = 0;
1739
+ __m256i sign_bit_i64x4 = _mm256_set1_epi64x((nk_i64_t)0x8000000000000000ULL);
1740
+ nk_size_t idx = 0;
1741
+ for (; idx + 8 <= count; idx += 8) {
1742
+ __m256i data_i32x8 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
1743
+ // 128-bit sum: lo half
1744
+ __m256i widened_low_i64x4 = _mm256_cvtepi32_epi64(_mm256_castsi256_si128(data_i32x8));
1745
+ __m256i sum_before_i64x4 = sum_low_i64x4;
1746
+ sum_low_i64x4 = _mm256_add_epi64(sum_low_i64x4, widened_low_i64x4);
1747
+ __m256i result_biased_i64x4 = _mm256_xor_si256(sum_low_i64x4, sign_bit_i64x4);
1748
+ __m256i before_biased_i64x4 = _mm256_xor_si256(sum_before_i64x4, sign_bit_i64x4);
1749
+ __m256i carry_mask_i64x4 = _mm256_cmpgt_epi64(before_biased_i64x4, result_biased_i64x4);
1750
+ sum_high_i64x4 = _mm256_sub_epi64(sum_high_i64x4, carry_mask_i64x4);
1751
+ __m256i sign_ext_i64x4 = _mm256_cmpgt_epi64(_mm256_setzero_si256(), widened_low_i64x4);
1752
+ sum_high_i64x4 = _mm256_add_epi64(sum_high_i64x4, sign_ext_i64x4);
1753
+ // 128-bit sum: hi half
1754
+ __m256i widened_high_i64x4 = _mm256_cvtepi32_epi64(_mm256_extracti128_si256(data_i32x8, 1));
1755
+ sum_before_i64x4 = sum_low_i64x4;
1756
+ sum_low_i64x4 = _mm256_add_epi64(sum_low_i64x4, widened_high_i64x4);
1757
+ result_biased_i64x4 = _mm256_xor_si256(sum_low_i64x4, sign_bit_i64x4);
1758
+ before_biased_i64x4 = _mm256_xor_si256(sum_before_i64x4, sign_bit_i64x4);
1759
+ carry_mask_i64x4 = _mm256_cmpgt_epi64(before_biased_i64x4, result_biased_i64x4);
1760
+ sum_high_i64x4 = _mm256_sub_epi64(sum_high_i64x4, carry_mask_i64x4);
1761
+ sign_ext_i64x4 = _mm256_cmpgt_epi64(_mm256_setzero_si256(), widened_high_i64x4);
1762
+ sum_high_i64x4 = _mm256_add_epi64(sum_high_i64x4, sign_ext_i64x4);
1763
+ // Sumsq: running mask + wrapping add with unsigned carry detection
1764
+ __m256i even_sq_u64x4 = _mm256_mul_epi32(data_i32x8, data_i32x8);
1765
+ __m256i odd_i32x8 = _mm256_srli_epi64(data_i32x8, 32);
1766
+ __m256i odd_sq_u64x4 = _mm256_mul_epi32(odd_i32x8, odd_i32x8);
1767
+ __m256i sumsq_before_u64x4 = sumsq_u64x4;
1768
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, even_sq_u64x4);
1769
+ __m256i sq_result_biased_u64x4 = _mm256_xor_si256(sumsq_u64x4, sign_bit_i64x4);
1770
+ __m256i sq_before_biased_u64x4 = _mm256_xor_si256(sumsq_before_u64x4, sign_bit_i64x4);
1771
+ sumsq_overflow_mask |= _mm256_movemask_pd(
1772
+ _mm256_castsi256_pd(_mm256_cmpgt_epi64(sq_before_biased_u64x4, sq_result_biased_u64x4)));
1773
+ sumsq_before_u64x4 = sumsq_u64x4;
1774
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, odd_sq_u64x4);
1775
+ sq_result_biased_u64x4 = _mm256_xor_si256(sumsq_u64x4, sign_bit_i64x4);
1776
+ sq_before_biased_u64x4 = _mm256_xor_si256(sumsq_before_u64x4, sign_bit_i64x4);
1777
+ sumsq_overflow_mask |= _mm256_movemask_pd(
1778
+ _mm256_castsi256_pd(_mm256_cmpgt_epi64(sq_before_biased_u64x4, sq_result_biased_u64x4)));
1779
+ }
1780
+ nk_size_t remaining = count - idx;
1781
+ if (remaining > 0) {
1782
+ nk_b256_vec_t tail_vec;
1783
+ nk_partial_load_b32x8_serial_(data_ptr + idx, &tail_vec, remaining);
1784
+ __m256i data_i32x8 = tail_vec.ymm;
1785
+ __m256i widened_low_i64x4 = _mm256_cvtepi32_epi64(_mm256_castsi256_si128(data_i32x8));
1786
+ __m256i sum_before_i64x4 = sum_low_i64x4;
1787
+ sum_low_i64x4 = _mm256_add_epi64(sum_low_i64x4, widened_low_i64x4);
1788
+ __m256i result_biased_i64x4 = _mm256_xor_si256(sum_low_i64x4, sign_bit_i64x4);
1789
+ __m256i before_biased_i64x4 = _mm256_xor_si256(sum_before_i64x4, sign_bit_i64x4);
1790
+ __m256i carry_mask_i64x4 = _mm256_cmpgt_epi64(before_biased_i64x4, result_biased_i64x4);
1791
+ sum_high_i64x4 = _mm256_sub_epi64(sum_high_i64x4, carry_mask_i64x4);
1792
+ __m256i sign_ext_i64x4 = _mm256_cmpgt_epi64(_mm256_setzero_si256(), widened_low_i64x4);
1793
+ sum_high_i64x4 = _mm256_add_epi64(sum_high_i64x4, sign_ext_i64x4);
1794
+ __m256i widened_high_i64x4 = _mm256_cvtepi32_epi64(_mm256_extracti128_si256(data_i32x8, 1));
1795
+ sum_before_i64x4 = sum_low_i64x4;
1796
+ sum_low_i64x4 = _mm256_add_epi64(sum_low_i64x4, widened_high_i64x4);
1797
+ result_biased_i64x4 = _mm256_xor_si256(sum_low_i64x4, sign_bit_i64x4);
1798
+ before_biased_i64x4 = _mm256_xor_si256(sum_before_i64x4, sign_bit_i64x4);
1799
+ carry_mask_i64x4 = _mm256_cmpgt_epi64(before_biased_i64x4, result_biased_i64x4);
1800
+ sum_high_i64x4 = _mm256_sub_epi64(sum_high_i64x4, carry_mask_i64x4);
1801
+ sign_ext_i64x4 = _mm256_cmpgt_epi64(_mm256_setzero_si256(), widened_high_i64x4);
1802
+ sum_high_i64x4 = _mm256_add_epi64(sum_high_i64x4, sign_ext_i64x4);
1803
+ __m256i even_sq_u64x4 = _mm256_mul_epi32(data_i32x8, data_i32x8);
1804
+ __m256i odd_i32x8 = _mm256_srli_epi64(data_i32x8, 32);
1805
+ __m256i odd_sq_u64x4 = _mm256_mul_epi32(odd_i32x8, odd_i32x8);
1806
+ __m256i sumsq_before_u64x4 = sumsq_u64x4;
1807
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, even_sq_u64x4);
1808
+ __m256i sq_result_biased_u64x4 = _mm256_xor_si256(sumsq_u64x4, sign_bit_i64x4);
1809
+ __m256i sq_before_biased_u64x4 = _mm256_xor_si256(sumsq_before_u64x4, sign_bit_i64x4);
1810
+ sumsq_overflow_mask |= _mm256_movemask_pd(
1811
+ _mm256_castsi256_pd(_mm256_cmpgt_epi64(sq_before_biased_u64x4, sq_result_biased_u64x4)));
1812
+ sumsq_before_u64x4 = sumsq_u64x4;
1813
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, odd_sq_u64x4);
1814
+ sq_result_biased_u64x4 = _mm256_xor_si256(sumsq_u64x4, sign_bit_i64x4);
1815
+ sq_before_biased_u64x4 = _mm256_xor_si256(sumsq_before_u64x4, sign_bit_i64x4);
1816
+ sumsq_overflow_mask |= _mm256_movemask_pd(
1817
+ _mm256_castsi256_pd(_mm256_cmpgt_epi64(sq_before_biased_u64x4, sq_result_biased_u64x4)));
1818
+ }
1819
+ // Sumsq: horizontal unsigned saturating reduction
1820
+ nk_u64_t sumsq;
1821
+ if (sumsq_overflow_mask) sumsq = NK_U64_MAX;
1822
+ else sumsq = nk_reduce_sadd_u64x4_haswell_(sumsq_u64x4);
1823
+ // Sum: horizontal 128-bit reduction (4 lanes → scalar)
1824
+ nk_b256_vec_t lower_vec, upper_vec;
1825
+ lower_vec.ymm = sum_low_i64x4;
1826
+ upper_vec.ymm = sum_high_i64x4;
1827
+ nk_u64_t sum_low = 0;
1828
+ nk_i64_t sum_high = 0;
1829
+ for (int i = 0; i < 4; i++) {
1830
+ nk_u64_t sum_before = sum_low;
1831
+ sum_low += lower_vec.u64s[i];
1832
+ if (sum_low < sum_before) sum_high++;
1833
+ sum_high += upper_vec.i64s[i];
1834
+ }
1835
+ *sumsq_ptr = sumsq;
1836
+ nk_i64_t sum_low_signed = (nk_i64_t)sum_low;
1837
+ if (sum_high == (sum_low_signed >> 63)) *sum_ptr = sum_low_signed;
1838
+ else if (sum_high >= 0) *sum_ptr = NK_I64_MAX;
1839
+ else *sum_ptr = NK_I64_MIN;
1840
+ }
1841
+
1842
+ NK_PUBLIC void nk_reduce_moments_i32_haswell( //
1843
+ nk_i32_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1844
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1845
+
1846
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i32_t);
1847
+ int aligned = (stride_bytes % sizeof(nk_i32_t) == 0);
1848
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
1849
+ else if (!aligned) nk_reduce_moments_i32_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1850
+ else if (stride_elements == 1) nk_reduce_moments_i32_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
1851
+ else nk_reduce_moments_i32_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1852
+ }
1853
+
1854
+ NK_INTERNAL void nk_reduce_minmax_i32_haswell_contiguous_( //
1855
+ nk_i32_t const *data_ptr, nk_size_t count, //
1856
+ nk_i32_t *min_value_ptr, nk_size_t *min_index_ptr, //
1857
+ nk_i32_t *max_value_ptr, nk_size_t *max_index_ptr) {
1858
+
1859
+ __m256i min_i32x8 = _mm256_set1_epi32(NK_I32_MAX);
1860
+ __m256i max_i32x8 = _mm256_set1_epi32(NK_I32_MIN);
1861
+ __m256i min_loop_cycle_u32x8 = _mm256_setzero_si256();
1862
+ __m256i max_loop_cycle_u32x8 = _mm256_setzero_si256();
1863
+ __m256i current_loop_cycle_u32x8 = _mm256_setzero_si256();
1864
+ __m256i one_u32x8 = _mm256_set1_epi32(1);
1865
+
1866
+ nk_size_t idx = 0;
1867
+ for (; idx + 8 <= count; idx += 8) {
1868
+ __m256i data_i32x8 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
1869
+ __m256i less_b32x8 = _mm256_cmpgt_epi32(min_i32x8, data_i32x8);
1870
+ __m256i greater_b32x8 = _mm256_cmpgt_epi32(data_i32x8, max_i32x8);
1871
+ min_i32x8 = _mm256_blendv_epi8(min_i32x8, data_i32x8, less_b32x8);
1872
+ max_i32x8 = _mm256_blendv_epi8(max_i32x8, data_i32x8, greater_b32x8);
1873
+ min_loop_cycle_u32x8 = _mm256_blendv_epi8(min_loop_cycle_u32x8, current_loop_cycle_u32x8, less_b32x8);
1874
+ max_loop_cycle_u32x8 = _mm256_blendv_epi8(max_loop_cycle_u32x8, current_loop_cycle_u32x8, greater_b32x8);
1875
+ current_loop_cycle_u32x8 = _mm256_add_epi32(current_loop_cycle_u32x8, one_u32x8);
1876
+ }
1877
+
1878
+ nk_size_t remaining = count - idx;
1879
+ if (remaining > 0) {
1880
+ nk_b256_vec_t tail_vec;
1881
+ nk_partial_load_b32x8_serial_(data_ptr + idx, &tail_vec, remaining);
1882
+ __m256i lane_indices_u32x8 = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
1883
+ __m256i valid_b32x8 = _mm256_cmpgt_epi32(_mm256_set1_epi32((int)remaining), lane_indices_u32x8);
1884
+ __m256i data_min_i32x8 = _mm256_blendv_epi8(_mm256_set1_epi32(NK_I32_MAX), tail_vec.ymm, valid_b32x8);
1885
+ __m256i data_max_i32x8 = _mm256_blendv_epi8(_mm256_set1_epi32(NK_I32_MIN), tail_vec.ymm, valid_b32x8);
1886
+ __m256i less_b32x8 = _mm256_cmpgt_epi32(min_i32x8, data_min_i32x8);
1887
+ __m256i greater_b32x8 = _mm256_cmpgt_epi32(data_max_i32x8, max_i32x8);
1888
+ min_i32x8 = _mm256_blendv_epi8(min_i32x8, data_min_i32x8, less_b32x8);
1889
+ max_i32x8 = _mm256_blendv_epi8(max_i32x8, data_max_i32x8, greater_b32x8);
1890
+ min_loop_cycle_u32x8 = _mm256_blendv_epi8(min_loop_cycle_u32x8, current_loop_cycle_u32x8, less_b32x8);
1891
+ max_loop_cycle_u32x8 = _mm256_blendv_epi8(max_loop_cycle_u32x8, current_loop_cycle_u32x8, greater_b32x8);
1892
+ }
1893
+
1894
+ nk_i32_t min_value = nk_reduce_min_i32x8_haswell_(min_i32x8);
1895
+ nk_i32_t max_value = nk_reduce_max_i32x8_haswell_(max_i32x8);
1896
+ unsigned int min_lane, max_lane;
1897
+ {
1898
+ __m256i value_match_b32x8 = _mm256_cmpeq_epi32(min_i32x8, _mm256_set1_epi32(min_value));
1899
+ __m256i masked_cycle_u32x8 = _mm256_blendv_epi8(_mm256_set1_epi32((int)NK_U32_MAX), min_loop_cycle_u32x8,
1900
+ value_match_b32x8);
1901
+ nk_u32_t earliest_loop_cycle = nk_reduce_min_u32x8_haswell_(masked_cycle_u32x8);
1902
+ __m256i cycle_match_b32x8 = _mm256_cmpeq_epi32(masked_cycle_u32x8, _mm256_set1_epi32((int)earliest_loop_cycle));
1903
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_ps(_mm256_castsi256_ps(cycle_match_b32x8)));
1904
+ }
1905
+ {
1906
+ __m256i value_match_b32x8 = _mm256_cmpeq_epi32(max_i32x8, _mm256_set1_epi32(max_value));
1907
+ __m256i masked_cycle_u32x8 = _mm256_blendv_epi8(_mm256_set1_epi32((int)NK_U32_MAX), max_loop_cycle_u32x8,
1908
+ value_match_b32x8);
1909
+ nk_u32_t earliest_loop_cycle = nk_reduce_min_u32x8_haswell_(masked_cycle_u32x8);
1910
+ __m256i cycle_match_b32x8 = _mm256_cmpeq_epi32(masked_cycle_u32x8, _mm256_set1_epi32((int)earliest_loop_cycle));
1911
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_ps(_mm256_castsi256_ps(cycle_match_b32x8)));
1912
+ }
1913
+ nk_b256_vec_t loop_cycle_vec;
1914
+ loop_cycle_vec.ymm = min_loop_cycle_u32x8;
1915
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u32s[min_lane] * 8 + min_lane;
1916
+ loop_cycle_vec.ymm = max_loop_cycle_u32x8;
1917
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u32s[max_lane] * 8 + max_lane;
1918
+ }
1919
+
1920
+ NK_PUBLIC void nk_reduce_minmax_i32_haswell( //
1921
+ nk_i32_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1922
+ nk_i32_t *min_value_ptr, nk_size_t *min_index_ptr, //
1923
+ nk_i32_t *max_value_ptr, nk_size_t *max_index_ptr) {
1924
+
1925
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i32_t);
1926
+ int aligned = (stride_bytes % sizeof(nk_i32_t) == 0);
1927
+ if (count == 0)
1928
+ *min_value_ptr = NK_I32_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_I32_MIN,
1929
+ *max_index_ptr = NK_SIZE_MAX;
1930
+ else if (!aligned)
1931
+ nk_reduce_minmax_i32_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1932
+ max_index_ptr);
1933
+ else if (count > (nk_size_t)NK_U32_MAX * 8) {
1934
+ nk_size_t left_count = count / 2;
1935
+ nk_i32_t left_min, right_min, left_max, right_max;
1936
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
1937
+ nk_reduce_minmax_i32_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
1938
+ &left_max_index);
1939
+ nk_reduce_minmax_i32_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
1940
+ &right_min, &right_min_index, &right_max, &right_max_index);
1941
+ if (right_min < left_min) *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
1942
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
1943
+ if (right_max > left_max) *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
1944
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
1945
+ }
1946
+ else if (stride_elements == 1)
1947
+ nk_reduce_minmax_i32_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
1948
+ max_index_ptr);
1949
+ else
1950
+ nk_reduce_minmax_i32_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
1951
+ max_index_ptr);
1952
+ }
1953
+
1954
+ NK_INTERNAL void nk_reduce_moments_u32_haswell_contiguous_( //
1955
+ nk_u32_t const *data_ptr, nk_size_t count, //
1956
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1957
+
1958
+ __m256i sum_u64x4 = _mm256_setzero_si256();
1959
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
1960
+ nk_size_t idx = 0;
1961
+ for (; idx + 8 <= count; idx += 8) {
1962
+ __m256i data_u32x8 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
1963
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_cvtepu32_epi64(_mm256_castsi256_si128(data_u32x8)));
1964
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(data_u32x8, 1)));
1965
+ __m256i even_sq_u64x4 = _mm256_mul_epu32(data_u32x8, data_u32x8);
1966
+ __m256i odd_u32x8 = _mm256_srli_epi64(data_u32x8, 32);
1967
+ __m256i odd_sq_u64x4 = _mm256_mul_epu32(odd_u32x8, odd_u32x8);
1968
+ sumsq_u64x4 = nk_u64_sadd_epi64_haswell_(sumsq_u64x4, even_sq_u64x4);
1969
+ sumsq_u64x4 = nk_u64_sadd_epi64_haswell_(sumsq_u64x4, odd_sq_u64x4);
1970
+ }
1971
+ nk_size_t remaining = count - idx;
1972
+ if (remaining > 0) {
1973
+ nk_b256_vec_t tail_vec;
1974
+ nk_partial_load_b32x8_serial_(data_ptr + idx, &tail_vec, remaining);
1975
+ __m256i data_u32x8 = tail_vec.ymm;
1976
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_cvtepu32_epi64(_mm256_castsi256_si128(data_u32x8)));
1977
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_cvtepu32_epi64(_mm256_extracti128_si256(data_u32x8, 1)));
1978
+ __m256i even_sq_u64x4 = _mm256_mul_epu32(data_u32x8, data_u32x8);
1979
+ __m256i odd_u32x8 = _mm256_srli_epi64(data_u32x8, 32);
1980
+ __m256i odd_sq_u64x4 = _mm256_mul_epu32(odd_u32x8, odd_u32x8);
1981
+ sumsq_u64x4 = nk_u64_sadd_epi64_haswell_(sumsq_u64x4, even_sq_u64x4);
1982
+ sumsq_u64x4 = nk_u64_sadd_epi64_haswell_(sumsq_u64x4, odd_sq_u64x4);
1983
+ }
1984
+ *sum_ptr = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4),
1985
+ *sumsq_ptr = nk_reduce_sadd_u64x4_haswell_(sumsq_u64x4);
1986
+ }
1987
+
1988
+ NK_PUBLIC void nk_reduce_moments_u32_haswell( //
1989
+ nk_u32_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
1990
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
1991
+
1992
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u32_t);
1993
+ int aligned = (stride_bytes % sizeof(nk_u32_t) == 0);
1994
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
1995
+ else if (!aligned) nk_reduce_moments_u32_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
1996
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 8) {
1997
+ nk_size_t left_count = count / 2;
1998
+ nk_u64_t left_sum, left_sumsq, right_sum, right_sumsq;
1999
+ nk_reduce_moments_u32_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
2000
+ nk_reduce_moments_u32_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
2001
+ &right_sum, &right_sumsq);
2002
+ *sum_ptr = nk_u64_saturating_add_serial(left_sum, right_sum);
2003
+ *sumsq_ptr = nk_u64_saturating_add_serial(left_sumsq, right_sumsq);
2004
+ }
2005
+ else if (stride_elements == 1) nk_reduce_moments_u32_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
2006
+ else nk_reduce_moments_u32_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2007
+ }
2008
+
2009
+ NK_INTERNAL void nk_reduce_minmax_u32_haswell_contiguous_( //
2010
+ nk_u32_t const *data_ptr, nk_size_t count, //
2011
+ nk_u32_t *min_value_ptr, nk_size_t *min_index_ptr, //
2012
+ nk_u32_t *max_value_ptr, nk_size_t *max_index_ptr) {
2013
+
2014
+ // XOR-bias to signed domain for _mm256_cmpgt_epi32
2015
+ __m256i bias_u32x8 = _mm256_set1_epi32((nk_i32_t)0x80000000);
2016
+ __m256i min_biased_i32x8 = _mm256_set1_epi32(NK_I32_MAX);
2017
+ __m256i max_biased_i32x8 = _mm256_set1_epi32(NK_I32_MIN);
2018
+ __m256i min_loop_cycle_u32x8 = _mm256_setzero_si256();
2019
+ __m256i max_loop_cycle_u32x8 = _mm256_setzero_si256();
2020
+ __m256i current_loop_cycle_u32x8 = _mm256_setzero_si256();
2021
+ __m256i one_u32x8 = _mm256_set1_epi32(1);
2022
+
2023
+ nk_size_t idx = 0;
2024
+ for (; idx + 8 <= count; idx += 8) {
2025
+ __m256i data_biased_i32x8 = _mm256_xor_si256(_mm256_loadu_si256((__m256i const *)(data_ptr + idx)), bias_u32x8);
2026
+ __m256i less_b32x8 = _mm256_cmpgt_epi32(min_biased_i32x8, data_biased_i32x8);
2027
+ __m256i greater_b32x8 = _mm256_cmpgt_epi32(data_biased_i32x8, max_biased_i32x8);
2028
+ min_biased_i32x8 = _mm256_blendv_epi8(min_biased_i32x8, data_biased_i32x8, less_b32x8);
2029
+ max_biased_i32x8 = _mm256_blendv_epi8(max_biased_i32x8, data_biased_i32x8, greater_b32x8);
2030
+ min_loop_cycle_u32x8 = _mm256_blendv_epi8(min_loop_cycle_u32x8, current_loop_cycle_u32x8, less_b32x8);
2031
+ max_loop_cycle_u32x8 = _mm256_blendv_epi8(max_loop_cycle_u32x8, current_loop_cycle_u32x8, greater_b32x8);
2032
+ current_loop_cycle_u32x8 = _mm256_add_epi32(current_loop_cycle_u32x8, one_u32x8);
2033
+ }
2034
+
2035
+ nk_size_t remaining = count - idx;
2036
+ if (remaining > 0) {
2037
+ nk_b256_vec_t tail_vec;
2038
+ nk_partial_load_b32x8_serial_(data_ptr + idx, &tail_vec, remaining);
2039
+ __m256i data_biased_i32x8 = _mm256_xor_si256(tail_vec.ymm, bias_u32x8);
2040
+ __m256i lane_indices_u32x8 = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
2041
+ __m256i valid_b32x8 = _mm256_cmpgt_epi32(_mm256_set1_epi32((int)remaining), lane_indices_u32x8);
2042
+ // Biased identity: NK_U32_MAX ^ 0x80000000 = 0x7FFFFFFF for min, 0 ^ 0x80000000 = 0x80000000 for max
2043
+ __m256i data_min_i32x8 = _mm256_blendv_epi8(_mm256_set1_epi32(0x7FFFFFFF), data_biased_i32x8, valid_b32x8);
2044
+ __m256i data_max_i32x8 = _mm256_blendv_epi8(_mm256_set1_epi32((nk_i32_t)0x80000000), data_biased_i32x8,
2045
+ valid_b32x8);
2046
+ __m256i less_b32x8 = _mm256_cmpgt_epi32(min_biased_i32x8, data_min_i32x8);
2047
+ __m256i greater_b32x8 = _mm256_cmpgt_epi32(data_max_i32x8, max_biased_i32x8);
2048
+ min_biased_i32x8 = _mm256_blendv_epi8(min_biased_i32x8, data_min_i32x8, less_b32x8);
2049
+ max_biased_i32x8 = _mm256_blendv_epi8(max_biased_i32x8, data_max_i32x8, greater_b32x8);
2050
+ min_loop_cycle_u32x8 = _mm256_blendv_epi8(min_loop_cycle_u32x8, current_loop_cycle_u32x8, less_b32x8);
2051
+ max_loop_cycle_u32x8 = _mm256_blendv_epi8(max_loop_cycle_u32x8, current_loop_cycle_u32x8, greater_b32x8);
2052
+ }
2053
+
2054
+ __m256i min_u32x8 = _mm256_xor_si256(min_biased_i32x8, bias_u32x8);
2055
+ __m256i max_u32x8 = _mm256_xor_si256(max_biased_i32x8, bias_u32x8);
2056
+ nk_u32_t min_value = nk_reduce_min_u32x8_haswell_(min_u32x8);
2057
+ nk_u32_t max_value = nk_reduce_max_u32x8_haswell_(max_u32x8);
2058
+ unsigned int min_lane, max_lane;
2059
+ {
2060
+ __m256i value_match_b32x8 = _mm256_cmpeq_epi32(min_u32x8, _mm256_set1_epi32((nk_i32_t)min_value));
2061
+ __m256i masked_cycle_u32x8 = _mm256_blendv_epi8(_mm256_set1_epi32((int)NK_U32_MAX), min_loop_cycle_u32x8,
2062
+ value_match_b32x8);
2063
+ nk_u32_t earliest_loop_cycle = nk_reduce_min_u32x8_haswell_(masked_cycle_u32x8);
2064
+ __m256i cycle_match_b32x8 = _mm256_cmpeq_epi32(masked_cycle_u32x8, _mm256_set1_epi32((int)earliest_loop_cycle));
2065
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_ps(_mm256_castsi256_ps(cycle_match_b32x8)));
2066
+ }
2067
+ {
2068
+ __m256i value_match_b32x8 = _mm256_cmpeq_epi32(max_u32x8, _mm256_set1_epi32((nk_i32_t)max_value));
2069
+ __m256i masked_cycle_u32x8 = _mm256_blendv_epi8(_mm256_set1_epi32((int)NK_U32_MAX), max_loop_cycle_u32x8,
2070
+ value_match_b32x8);
2071
+ nk_u32_t earliest_loop_cycle = nk_reduce_min_u32x8_haswell_(masked_cycle_u32x8);
2072
+ __m256i cycle_match_b32x8 = _mm256_cmpeq_epi32(masked_cycle_u32x8, _mm256_set1_epi32((int)earliest_loop_cycle));
2073
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_ps(_mm256_castsi256_ps(cycle_match_b32x8)));
2074
+ }
2075
+ nk_b256_vec_t loop_cycle_vec;
2076
+ loop_cycle_vec.ymm = min_loop_cycle_u32x8;
2077
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u32s[min_lane] * 8 + min_lane;
2078
+ loop_cycle_vec.ymm = max_loop_cycle_u32x8;
2079
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u32s[max_lane] * 8 + max_lane;
2080
+ }
2081
+
2082
+ NK_PUBLIC void nk_reduce_minmax_u32_haswell( //
2083
+ nk_u32_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2084
+ nk_u32_t *min_value_ptr, nk_size_t *min_index_ptr, //
2085
+ nk_u32_t *max_value_ptr, nk_size_t *max_index_ptr) {
2086
+
2087
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u32_t);
2088
+ int aligned = (stride_bytes % sizeof(nk_u32_t) == 0);
2089
+ if (count == 0)
2090
+ *min_value_ptr = NK_U32_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = 0, *max_index_ptr = NK_SIZE_MAX;
2091
+ else if (!aligned)
2092
+ nk_reduce_minmax_u32_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2093
+ max_index_ptr);
2094
+ else if (count > (nk_size_t)NK_U32_MAX * 8) {
2095
+ nk_size_t left_count = count / 2;
2096
+ nk_u32_t left_min, right_min, left_max, right_max;
2097
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
2098
+ nk_reduce_minmax_u32_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
2099
+ &left_max_index);
2100
+ nk_reduce_minmax_u32_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
2101
+ &right_min, &right_min_index, &right_max, &right_max_index);
2102
+ if (right_min < left_min) *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
2103
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
2104
+ if (right_max > left_max) *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
2105
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
2106
+ }
2107
+ else if (stride_elements == 1)
2108
+ nk_reduce_minmax_u32_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
2109
+ max_index_ptr);
2110
+ else
2111
+ nk_reduce_minmax_u32_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2112
+ max_index_ptr);
2113
+ }
2114
+
2115
+ NK_INTERNAL void nk_reduce_moments_i64_haswell_contiguous_( //
2116
+ nk_i64_t const *data_ptr, nk_size_t count, //
2117
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
2118
+
2119
+ __m256i sum_low_u64x4 = _mm256_setzero_si256();
2120
+ __m256i sum_high_i64x4 = _mm256_setzero_si256();
2121
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
2122
+ int sumsq_overflow_mask = 0;
2123
+ __m256i sign_bit_i64x4 = _mm256_set1_epi64x((nk_i64_t)0x8000000000000000ULL);
2124
+ nk_size_t idx = 0;
2125
+ for (; idx + 4 <= count; idx += 4) {
2126
+ __m256i data_i64x4 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
2127
+ __m256i squared_u64x4 = nk_i64_smul_sq_epi64_haswell_(data_i64x4);
2128
+ __m256i sumsq_before_u64x4 = sumsq_u64x4;
2129
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, squared_u64x4);
2130
+ __m256i sq_result_biased_u64x4 = _mm256_xor_si256(sumsq_u64x4, sign_bit_i64x4);
2131
+ __m256i sq_before_biased_u64x4 = _mm256_xor_si256(sumsq_before_u64x4, sign_bit_i64x4);
2132
+ sumsq_overflow_mask |= _mm256_movemask_pd(
2133
+ _mm256_castsi256_pd(_mm256_cmpgt_epi64(sq_before_biased_u64x4, sq_result_biased_u64x4)));
2134
+ // Vectorized 128-bit carry-propagating sum
2135
+ __m256i sum_before_u64x4 = sum_low_u64x4;
2136
+ sum_low_u64x4 = _mm256_add_epi64(sum_low_u64x4, data_i64x4);
2137
+ __m256i before_biased_u64x4 = _mm256_xor_si256(sum_before_u64x4, sign_bit_i64x4);
2138
+ __m256i result_biased_u64x4 = _mm256_xor_si256(sum_low_u64x4, sign_bit_i64x4);
2139
+ __m256i carry_u64x4 = _mm256_cmpgt_epi64(before_biased_u64x4, result_biased_u64x4);
2140
+ sum_high_i64x4 = _mm256_sub_epi64(sum_high_i64x4, carry_u64x4);
2141
+ __m256i sign_ext_i64x4 = _mm256_cmpgt_epi64(_mm256_setzero_si256(), data_i64x4);
2142
+ sum_high_i64x4 = _mm256_add_epi64(sum_high_i64x4, sign_ext_i64x4);
2143
+ }
2144
+ // Horizontal reduction of 4 lanes to scalar (sum_low, sum_high)
2145
+ nk_b256_vec_t lower_vec, upper_vec;
2146
+ lower_vec.ymm = sum_low_u64x4;
2147
+ upper_vec.ymm = sum_high_i64x4;
2148
+ nk_u64_t sum_low = 0;
2149
+ nk_i64_t sum_high = 0;
2150
+ for (int i = 0; i < 4; i++) {
2151
+ nk_u64_t before = sum_low;
2152
+ sum_low += lower_vec.u64s[i];
2153
+ if (sum_low < before) sum_high++;
2154
+ sum_high += upper_vec.i64s[i];
2155
+ }
2156
+ nk_u64_t sumsq;
2157
+ if (sumsq_overflow_mask) sumsq = NK_U64_MAX;
2158
+ else sumsq = nk_reduce_sadd_u64x4_haswell_(sumsq_u64x4);
2159
+ for (; idx < count; ++idx) {
2160
+ nk_i64_t val = data_ptr[idx];
2161
+ nk_i64_t product = nk_i64_saturating_mul_serial(val, val);
2162
+ nk_u64_t unsigned_product = (nk_u64_t)product;
2163
+ sumsq = nk_u64_saturating_add_serial(sumsq, unsigned_product);
2164
+ nk_u64_t before = sum_low;
2165
+ sum_low += (nk_u64_t)val;
2166
+ if (sum_low < before) sum_high++;
2167
+ sum_high += (val >> 63);
2168
+ }
2169
+ *sumsq_ptr = sumsq;
2170
+ nk_i64_t sum_low_signed = (nk_i64_t)sum_low;
2171
+ if (sum_high == (sum_low_signed >> 63)) *sum_ptr = sum_low_signed;
2172
+ else if (sum_high >= 0) *sum_ptr = NK_I64_MAX;
2173
+ else *sum_ptr = NK_I64_MIN;
2174
+ }
2175
+
2176
+ NK_PUBLIC void nk_reduce_moments_i64_haswell( //
2177
+ nk_i64_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2178
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
2179
+
2180
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i64_t);
2181
+ int aligned = (stride_bytes % sizeof(nk_i64_t) == 0);
2182
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
2183
+ else if (!aligned) nk_reduce_moments_i64_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2184
+ else if (stride_elements == 1) nk_reduce_moments_i64_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
2185
+ else nk_reduce_moments_i64_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2186
+ }
2187
+
2188
+ NK_INTERNAL void nk_reduce_minmax_i64_haswell_contiguous_( //
2189
+ nk_i64_t const *data_ptr, nk_size_t count, //
2190
+ nk_i64_t *min_value_ptr, nk_size_t *min_index_ptr, //
2191
+ nk_i64_t *max_value_ptr, nk_size_t *max_index_ptr) {
2192
+
2193
+ __m256i min_i64x4 = _mm256_set1_epi64x(NK_I64_MAX);
2194
+ __m256i max_i64x4 = _mm256_set1_epi64x(NK_I64_MIN);
2195
+ __m256i min_loop_cycle_u64x4 = _mm256_setzero_si256();
2196
+ __m256i max_loop_cycle_u64x4 = _mm256_setzero_si256();
2197
+ __m256i current_loop_cycle_u64x4 = _mm256_setzero_si256();
2198
+ __m256i one_u64x4 = _mm256_set1_epi64x(1);
2199
+
2200
+ nk_size_t idx = 0;
2201
+ for (; idx + 4 <= count; idx += 4) {
2202
+ __m256i data_i64x4 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
2203
+ __m256i less_b64x4 = _mm256_cmpgt_epi64(min_i64x4, data_i64x4);
2204
+ __m256i greater_b64x4 = _mm256_cmpgt_epi64(data_i64x4, max_i64x4);
2205
+ min_i64x4 = _mm256_blendv_epi8(min_i64x4, data_i64x4, less_b64x4);
2206
+ max_i64x4 = _mm256_blendv_epi8(max_i64x4, data_i64x4, greater_b64x4);
2207
+ min_loop_cycle_u64x4 = _mm256_blendv_epi8(min_loop_cycle_u64x4, current_loop_cycle_u64x4, less_b64x4);
2208
+ max_loop_cycle_u64x4 = _mm256_blendv_epi8(max_loop_cycle_u64x4, current_loop_cycle_u64x4, greater_b64x4);
2209
+ current_loop_cycle_u64x4 = _mm256_add_epi64(current_loop_cycle_u64x4, one_u64x4);
2210
+ }
2211
+
2212
+ nk_size_t remaining = count - idx;
2213
+ if (remaining > 0) {
2214
+ nk_b256_vec_t tail_vec;
2215
+ nk_partial_load_b64x4_serial_(data_ptr + idx, &tail_vec, remaining);
2216
+ __m256i lane_indices_u64x4 = _mm256_setr_epi64x(0, 1, 2, 3);
2217
+ __m256i valid_b64x4 = _mm256_cmpgt_epi64(_mm256_set1_epi64x((long long)remaining), lane_indices_u64x4);
2218
+ __m256i data_min_i64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x(NK_I64_MAX), tail_vec.ymm, valid_b64x4);
2219
+ __m256i data_max_i64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x(NK_I64_MIN), tail_vec.ymm, valid_b64x4);
2220
+ __m256i less_b64x4 = _mm256_cmpgt_epi64(min_i64x4, data_min_i64x4);
2221
+ __m256i greater_b64x4 = _mm256_cmpgt_epi64(data_max_i64x4, max_i64x4);
2222
+ min_i64x4 = _mm256_blendv_epi8(min_i64x4, data_min_i64x4, less_b64x4);
2223
+ max_i64x4 = _mm256_blendv_epi8(max_i64x4, data_max_i64x4, greater_b64x4);
2224
+ min_loop_cycle_u64x4 = _mm256_blendv_epi8(min_loop_cycle_u64x4, current_loop_cycle_u64x4, less_b64x4);
2225
+ max_loop_cycle_u64x4 = _mm256_blendv_epi8(max_loop_cycle_u64x4, current_loop_cycle_u64x4, greater_b64x4);
2226
+ }
2227
+
2228
+ nk_i64_t min_value = nk_reduce_min_i64x4_haswell_(min_i64x4);
2229
+ nk_i64_t max_value = nk_reduce_max_i64x4_haswell_(max_i64x4);
2230
+ unsigned int min_lane, max_lane;
2231
+ {
2232
+ __m256i value_match_b64x4 = _mm256_cmpeq_epi64(min_i64x4, _mm256_set1_epi64x(min_value));
2233
+ __m256i masked_cycle_u64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x((nk_i64_t)NK_U64_MAX), min_loop_cycle_u64x4,
2234
+ value_match_b64x4);
2235
+ nk_u64_t earliest_loop_cycle = nk_reduce_min_u64x4_haswell_(masked_cycle_u64x4);
2236
+ __m256i cycle_match_b64x4 = _mm256_cmpeq_epi64(masked_cycle_u64x4,
2237
+ _mm256_set1_epi64x((nk_i64_t)earliest_loop_cycle));
2238
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_pd(_mm256_castsi256_pd(cycle_match_b64x4)));
2239
+ }
2240
+ {
2241
+ __m256i value_match_b64x4 = _mm256_cmpeq_epi64(max_i64x4, _mm256_set1_epi64x(max_value));
2242
+ __m256i masked_cycle_u64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x((nk_i64_t)NK_U64_MAX), max_loop_cycle_u64x4,
2243
+ value_match_b64x4);
2244
+ nk_u64_t earliest_loop_cycle = nk_reduce_min_u64x4_haswell_(masked_cycle_u64x4);
2245
+ __m256i cycle_match_b64x4 = _mm256_cmpeq_epi64(masked_cycle_u64x4,
2246
+ _mm256_set1_epi64x((nk_i64_t)earliest_loop_cycle));
2247
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_pd(_mm256_castsi256_pd(cycle_match_b64x4)));
2248
+ }
2249
+ nk_b256_vec_t loop_cycle_vec;
2250
+ loop_cycle_vec.ymm = min_loop_cycle_u64x4;
2251
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u64s[min_lane] * 4 + min_lane;
2252
+ loop_cycle_vec.ymm = max_loop_cycle_u64x4;
2253
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u64s[max_lane] * 4 + max_lane;
2254
+ }
2255
+
2256
+ NK_PUBLIC void nk_reduce_minmax_i64_haswell( //
2257
+ nk_i64_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2258
+ nk_i64_t *min_value_ptr, nk_size_t *min_index_ptr, //
2259
+ nk_i64_t *max_value_ptr, nk_size_t *max_index_ptr) {
2260
+
2261
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_i64_t);
2262
+ int aligned = (stride_bytes % sizeof(nk_i64_t) == 0);
2263
+ if (count == 0)
2264
+ *min_value_ptr = NK_I64_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_I64_MIN,
2265
+ *max_index_ptr = NK_SIZE_MAX;
2266
+ else if (!aligned)
2267
+ nk_reduce_minmax_i64_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2268
+ max_index_ptr);
2269
+ else if (stride_elements == 1)
2270
+ nk_reduce_minmax_i64_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
2271
+ max_index_ptr);
2272
+ else
2273
+ nk_reduce_minmax_i64_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2274
+ max_index_ptr);
2275
+ }
2276
+
2277
+ NK_INTERNAL void nk_reduce_moments_u64_haswell_contiguous_( //
2278
+ nk_u64_t const *data_ptr, nk_size_t count, //
2279
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
2280
+
2281
+ __m256i sum_u64x4 = _mm256_setzero_si256();
2282
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
2283
+ nk_size_t idx = 0;
2284
+ for (; idx + 4 <= count; idx += 4) {
2285
+ __m256i data_u64x4 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
2286
+ sumsq_u64x4 = nk_u64_sadd_epi64_haswell_(sumsq_u64x4, nk_u64_smul_sq_epi64_haswell_(data_u64x4));
2287
+ sum_u64x4 = nk_u64_sadd_epi64_haswell_(sum_u64x4, data_u64x4);
2288
+ }
2289
+ nk_u64_t sum = nk_reduce_sadd_u64x4_haswell_(sum_u64x4);
2290
+ nk_u64_t sumsq = nk_reduce_sadd_u64x4_haswell_(sumsq_u64x4);
2291
+ for (; idx < count; ++idx) {
2292
+ nk_u64_t val = data_ptr[idx];
2293
+ nk_u64_t product = nk_u64_saturating_mul_serial(val, val);
2294
+ sum = nk_u64_saturating_add_serial(sum, val);
2295
+ sumsq = nk_u64_saturating_add_serial(sumsq, product);
2296
+ }
2297
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
2298
+ }
2299
+
2300
+ NK_PUBLIC void nk_reduce_moments_u64_haswell( //
2301
+ nk_u64_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2302
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
2303
+
2304
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u64_t);
2305
+ int aligned = (stride_bytes % sizeof(nk_u64_t) == 0);
2306
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
2307
+ else if (!aligned) nk_reduce_moments_u64_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2308
+ else if (stride_elements == 1) nk_reduce_moments_u64_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
2309
+ else nk_reduce_moments_u64_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2310
+ }
2311
+
2312
+ NK_INTERNAL void nk_reduce_minmax_u64_haswell_contiguous_( //
2313
+ nk_u64_t const *data_ptr, nk_size_t count, //
2314
+ nk_u64_t *min_value_ptr, nk_size_t *min_index_ptr, //
2315
+ nk_u64_t *max_value_ptr, nk_size_t *max_index_ptr) {
2316
+
2317
+ // XOR-bias to signed domain for _mm256_cmpgt_epi64
2318
+ __m256i bias_u64x4 = _mm256_set1_epi64x((nk_i64_t)0x8000000000000000ull);
2319
+ __m256i min_biased_i64x4 = _mm256_set1_epi64x(NK_I64_MAX);
2320
+ __m256i max_biased_i64x4 = _mm256_set1_epi64x(NK_I64_MIN);
2321
+ __m256i min_loop_cycle_u64x4 = _mm256_setzero_si256();
2322
+ __m256i max_loop_cycle_u64x4 = _mm256_setzero_si256();
2323
+ __m256i current_loop_cycle_u64x4 = _mm256_setzero_si256();
2324
+ __m256i one_u64x4 = _mm256_set1_epi64x(1);
2325
+
2326
+ nk_size_t idx = 0;
2327
+ for (; idx + 4 <= count; idx += 4) {
2328
+ __m256i data_biased_i64x4 = _mm256_xor_si256(_mm256_loadu_si256((__m256i const *)(data_ptr + idx)), bias_u64x4);
2329
+ __m256i less_b64x4 = _mm256_cmpgt_epi64(min_biased_i64x4, data_biased_i64x4);
2330
+ __m256i greater_b64x4 = _mm256_cmpgt_epi64(data_biased_i64x4, max_biased_i64x4);
2331
+ min_biased_i64x4 = _mm256_blendv_epi8(min_biased_i64x4, data_biased_i64x4, less_b64x4);
2332
+ max_biased_i64x4 = _mm256_blendv_epi8(max_biased_i64x4, data_biased_i64x4, greater_b64x4);
2333
+ min_loop_cycle_u64x4 = _mm256_blendv_epi8(min_loop_cycle_u64x4, current_loop_cycle_u64x4, less_b64x4);
2334
+ max_loop_cycle_u64x4 = _mm256_blendv_epi8(max_loop_cycle_u64x4, current_loop_cycle_u64x4, greater_b64x4);
2335
+ current_loop_cycle_u64x4 = _mm256_add_epi64(current_loop_cycle_u64x4, one_u64x4);
2336
+ }
2337
+
2338
+ nk_size_t remaining = count - idx;
2339
+ if (remaining > 0) {
2340
+ nk_b256_vec_t tail_vec;
2341
+ nk_partial_load_b64x4_serial_(data_ptr + idx, &tail_vec, remaining);
2342
+ __m256i data_biased_i64x4 = _mm256_xor_si256(tail_vec.ymm, bias_u64x4);
2343
+ __m256i lane_indices_u64x4 = _mm256_setr_epi64x(0, 1, 2, 3);
2344
+ __m256i valid_b64x4 = _mm256_cmpgt_epi64(_mm256_set1_epi64x((long long)remaining), lane_indices_u64x4);
2345
+ // Biased identity: NK_U64_MAX ^ bias = 0x7FFF... for min, 0 ^ bias = 0x8000... for max
2346
+ __m256i data_min_i64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x(NK_I64_MAX), data_biased_i64x4, valid_b64x4);
2347
+ __m256i data_max_i64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x(NK_I64_MIN), data_biased_i64x4, valid_b64x4);
2348
+ __m256i less_b64x4 = _mm256_cmpgt_epi64(min_biased_i64x4, data_min_i64x4);
2349
+ __m256i greater_b64x4 = _mm256_cmpgt_epi64(data_max_i64x4, max_biased_i64x4);
2350
+ min_biased_i64x4 = _mm256_blendv_epi8(min_biased_i64x4, data_min_i64x4, less_b64x4);
2351
+ max_biased_i64x4 = _mm256_blendv_epi8(max_biased_i64x4, data_max_i64x4, greater_b64x4);
2352
+ min_loop_cycle_u64x4 = _mm256_blendv_epi8(min_loop_cycle_u64x4, current_loop_cycle_u64x4, less_b64x4);
2353
+ max_loop_cycle_u64x4 = _mm256_blendv_epi8(max_loop_cycle_u64x4, current_loop_cycle_u64x4, greater_b64x4);
2354
+ }
2355
+
2356
+ __m256i min_u64x4 = _mm256_xor_si256(min_biased_i64x4, bias_u64x4);
2357
+ __m256i max_u64x4 = _mm256_xor_si256(max_biased_i64x4, bias_u64x4);
2358
+ nk_u64_t min_value = nk_reduce_min_u64x4_haswell_(min_u64x4);
2359
+ nk_u64_t max_value = nk_reduce_max_u64x4_haswell_(max_u64x4);
2360
+ unsigned int min_lane, max_lane;
2361
+ {
2362
+ __m256i value_match_b64x4 = _mm256_cmpeq_epi64(min_u64x4, _mm256_set1_epi64x((nk_i64_t)min_value));
2363
+ __m256i masked_cycle_u64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x((nk_i64_t)NK_U64_MAX), min_loop_cycle_u64x4,
2364
+ value_match_b64x4);
2365
+ nk_u64_t earliest_loop_cycle = nk_reduce_min_u64x4_haswell_(masked_cycle_u64x4);
2366
+ __m256i cycle_match_b64x4 = _mm256_cmpeq_epi64(masked_cycle_u64x4,
2367
+ _mm256_set1_epi64x((nk_i64_t)earliest_loop_cycle));
2368
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_pd(_mm256_castsi256_pd(cycle_match_b64x4)));
2369
+ }
2370
+ {
2371
+ __m256i value_match_b64x4 = _mm256_cmpeq_epi64(max_u64x4, _mm256_set1_epi64x((nk_i64_t)max_value));
2372
+ __m256i masked_cycle_u64x4 = _mm256_blendv_epi8(_mm256_set1_epi64x((nk_i64_t)NK_U64_MAX), max_loop_cycle_u64x4,
2373
+ value_match_b64x4);
2374
+ nk_u64_t earliest_loop_cycle = nk_reduce_min_u64x4_haswell_(masked_cycle_u64x4);
2375
+ __m256i cycle_match_b64x4 = _mm256_cmpeq_epi64(masked_cycle_u64x4,
2376
+ _mm256_set1_epi64x((nk_i64_t)earliest_loop_cycle));
2377
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_pd(_mm256_castsi256_pd(cycle_match_b64x4)));
2378
+ }
2379
+ nk_b256_vec_t loop_cycle_vec;
2380
+ loop_cycle_vec.ymm = min_loop_cycle_u64x4;
2381
+ *min_value_ptr = min_value, *min_index_ptr = (nk_size_t)loop_cycle_vec.u64s[min_lane] * 4 + min_lane;
2382
+ loop_cycle_vec.ymm = max_loop_cycle_u64x4;
2383
+ *max_value_ptr = max_value, *max_index_ptr = (nk_size_t)loop_cycle_vec.u64s[max_lane] * 4 + max_lane;
2384
+ }
2385
+
2386
+ NK_PUBLIC void nk_reduce_minmax_u64_haswell( //
2387
+ nk_u64_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2388
+ nk_u64_t *min_value_ptr, nk_size_t *min_index_ptr, //
2389
+ nk_u64_t *max_value_ptr, nk_size_t *max_index_ptr) {
2390
+
2391
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_u64_t);
2392
+ int aligned = (stride_bytes % sizeof(nk_u64_t) == 0);
2393
+ if (count == 0)
2394
+ *min_value_ptr = NK_U64_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = 0, *max_index_ptr = NK_SIZE_MAX;
2395
+ else if (!aligned)
2396
+ nk_reduce_minmax_u64_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2397
+ max_index_ptr);
2398
+ else if (stride_elements == 1)
2399
+ nk_reduce_minmax_u64_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
2400
+ max_index_ptr);
2401
+ else
2402
+ nk_reduce_minmax_u64_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2403
+ max_index_ptr);
2404
+ }
2405
+
2406
+ NK_INTERNAL void nk_reduce_moments_e4m3_haswell_contiguous_( //
2407
+ nk_e4m3_t const *data_ptr, nk_size_t count, //
2408
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2409
+
2410
+ __m256 sum_f32x8 = _mm256_setzero_ps();
2411
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
2412
+ nk_size_t idx = 0;
2413
+ for (; idx + 8 <= count; idx += 8) {
2414
+ __m256 data_f32x8 = nk_e4m3x8_to_f32x8_haswell_(_mm_loadl_epi64((__m128i const *)(data_ptr + idx)));
2415
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
2416
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
2417
+ }
2418
+ nk_size_t remaining = count - idx;
2419
+ if (remaining > 0) {
2420
+ nk_b256_vec_t vec;
2421
+ nk_partial_load_e4m3x8_to_f32x8_haswell_(data_ptr + idx, &vec, remaining);
2422
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
2423
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
2424
+ }
2425
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
2426
+ }
2427
+
2428
+ NK_INTERNAL void nk_reduce_moments_e4m3_haswell_strided_( //
2429
+ nk_e4m3_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
2430
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2431
+
2432
+ __m256 sum_f32x8 = _mm256_setzero_ps();
2433
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
2434
+ nk_size_t idx = 0;
2435
+ nk_size_t elems_per_chunk = 8;
2436
+ for (; idx + elems_per_chunk <= count; idx += elems_per_chunk) {
2437
+ nk_b64_vec_t buf;
2438
+ buf.u64 = 0;
2439
+ nk_e4m3_t const *ptr = data_ptr + idx * stride_elements;
2440
+ for (nk_size_t i = 0; i < elems_per_chunk; ++i) buf.u8s[i] = ptr[i * stride_elements];
2441
+ __m256 data_f32x8 = nk_e4m3x8_to_f32x8_haswell_(_mm_cvtsi64_si128(buf.u64));
2442
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
2443
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
2444
+ }
2445
+ nk_size_t remaining = count - idx;
2446
+ if (remaining > 0) {
2447
+ nk_b64_vec_t buf;
2448
+ buf.u64 = 0;
2449
+ nk_e4m3_t const *ptr = data_ptr + idx * stride_elements;
2450
+ for (nk_size_t i = 0; i < remaining; ++i) buf.u8s[i] = ptr[i * stride_elements];
2451
+ nk_b256_vec_t vec;
2452
+ nk_partial_load_e4m3x8_to_f32x8_haswell_((nk_e4m3_t *)&buf, &vec, remaining);
2453
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
2454
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
2455
+ }
2456
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
2457
+ }
2458
+
2459
+ NK_PUBLIC void nk_reduce_moments_e4m3_haswell( //
2460
+ nk_e4m3_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2461
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2462
+
2463
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e4m3_t);
2464
+ int aligned = (stride_bytes % sizeof(nk_e4m3_t) == 0);
2465
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
2466
+ else if (!aligned) nk_reduce_moments_e4m3_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2467
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 32) {
2468
+ nk_size_t left_count = count / 2;
2469
+ nk_f32_t left_sum, left_sumsq, right_sum, right_sumsq;
2470
+ nk_reduce_moments_e4m3_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
2471
+ nk_reduce_moments_e4m3_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
2472
+ &right_sum, &right_sumsq);
2473
+ *sum_ptr = left_sum + right_sum, *sumsq_ptr = left_sumsq + right_sumsq;
2474
+ }
2475
+ else if (stride_elements == 1) nk_reduce_moments_e4m3_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
2476
+ else if (stride_elements >= 2 && stride_elements <= 8)
2477
+ nk_reduce_moments_e4m3_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
2478
+ else nk_reduce_moments_e4m3_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2479
+ }
2480
+
2481
+ NK_INTERNAL void nk_reduce_minmax_e4m3_haswell_contiguous_( //
2482
+ nk_e4m3_t const *data_ptr, nk_size_t count, //
2483
+ nk_e4m3_t *min_value_ptr, nk_size_t *min_index_ptr, //
2484
+ nk_e4m3_t *max_value_ptr, nk_size_t *max_index_ptr) {
2485
+
2486
+ // E4M3 NaN: comparable 0x00 (neg NaN 0xFF) and 0xFF (pos NaN 0x7F).
2487
+ // Replace NaN lanes with neutral values: 0xFF for min, 0x00 for max.
2488
+ nk_b256_vec_t min_vec, max_vec;
2489
+ min_vec.ymm = _mm256_set1_epi8((char)0xFF);
2490
+ max_vec.ymm = _mm256_setzero_si256();
2491
+ __m256i min_loop_cycle_u8x32 = _mm256_setzero_si256();
2492
+ __m256i max_loop_cycle_u8x32 = _mm256_setzero_si256();
2493
+ __m256i current_loop_cycle_u8x32 = _mm256_setzero_si256();
2494
+ __m256i one_u8x32 = _mm256_set1_epi8(1);
2495
+ __m256i all_ones_u8x32 = _mm256_set1_epi8((char)0xFF);
2496
+
2497
+ nk_size_t idx = 0;
2498
+ for (; idx + 32 <= count; idx += 32) {
2499
+ __m256i data_i8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
2500
+ __m256i data_cmp_u8x32 = nk_fp8x32_to_u8x32_comparable_haswell_(data_i8x32);
2501
+ // Detect NaN: comparable == 0x00 (neg NaN) or comparable == 0xFF (pos NaN)
2502
+ __m256i is_nan_low_u8x32 = _mm256_cmpeq_epi8(data_cmp_u8x32, _mm256_setzero_si256());
2503
+ __m256i is_nan_high_u8x32 = _mm256_cmpeq_epi8(data_cmp_u8x32, all_ones_u8x32);
2504
+ __m256i is_nan_u8x32 = _mm256_or_si256(is_nan_low_u8x32, is_nan_high_u8x32);
2505
+ // Replace NaN with neutral values
2506
+ __m256i data_min_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, all_ones_u8x32, is_nan_u8x32);
2507
+ __m256i data_max_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, _mm256_setzero_si256(), is_nan_u8x32);
2508
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, data_min_u8x32);
2509
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm), all_ones_u8x32);
2510
+ min_vec.ymm = new_min_u8x32;
2511
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
2512
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, data_max_u8x32);
2513
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm), all_ones_u8x32);
2514
+ max_vec.ymm = new_max_u8x32;
2515
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
2516
+ current_loop_cycle_u8x32 = _mm256_add_epi8(current_loop_cycle_u8x32, one_u8x32);
2517
+ }
2518
+
2519
+ nk_size_t remaining = count - idx;
2520
+ if (remaining > 0) {
2521
+ nk_b256_vec_t tail_vec;
2522
+ tail_vec.ymm = _mm256_set1_epi8((char)0xFF);
2523
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
2524
+ for (nk_size_t i = remaining; i < 32; ++i) tail_vec.u8s[i] = 0xFF;
2525
+ __m256i data_cmp_u8x32 = nk_fp8x32_to_u8x32_comparable_haswell_(tail_vec.ymm);
2526
+ // Detect NaN in valid lanes, then combine with invalid-lane neutralization
2527
+ __m256i is_nan_low_u8x32 = _mm256_cmpeq_epi8(data_cmp_u8x32, _mm256_setzero_si256());
2528
+ __m256i is_nan_high_u8x32 = _mm256_cmpeq_epi8(data_cmp_u8x32, all_ones_u8x32);
2529
+ __m256i is_nan_u8x32 = _mm256_or_si256(is_nan_low_u8x32, is_nan_high_u8x32);
2530
+ __m256i data_min_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, all_ones_u8x32, is_nan_u8x32);
2531
+ __m256i data_max_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, _mm256_setzero_si256(), is_nan_u8x32);
2532
+ // Fill invalid lanes with neutral values
2533
+ nk_b256_vec_t min_cmp_vec, max_cmp_vec;
2534
+ min_cmp_vec.ymm = data_min_u8x32;
2535
+ max_cmp_vec.ymm = data_max_u8x32;
2536
+ for (nk_size_t i = remaining; i < 32; ++i) min_cmp_vec.u8s[i] = 0xFF, max_cmp_vec.u8s[i] = 0x00;
2537
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, min_cmp_vec.ymm);
2538
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm), all_ones_u8x32);
2539
+ min_vec.ymm = new_min_u8x32;
2540
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
2541
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, max_cmp_vec.ymm);
2542
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm), all_ones_u8x32);
2543
+ max_vec.ymm = new_max_u8x32;
2544
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
2545
+ }
2546
+
2547
+ nk_u8_t min_value_comparable = nk_reduce_min_u8x32_haswell_(min_vec.ymm);
2548
+ nk_u8_t max_value_comparable = nk_reduce_max_u8x32_haswell_(max_vec.ymm);
2549
+
2550
+ // All-NaN early return: both sentinels unchanged means no valid data was found
2551
+ if (min_value_comparable == 0xFF && max_value_comparable == 0x00) {
2552
+ *min_value_ptr = NK_E4M3_MAX, *min_index_ptr = NK_SIZE_MAX;
2553
+ *max_value_ptr = NK_E4M3_MIN, *max_index_ptr = NK_SIZE_MAX;
2554
+ return;
2555
+ }
2556
+
2557
+ if (min_value_comparable == 0xFF) { *min_value_ptr = NK_E4M3_MAX, *min_index_ptr = NK_SIZE_MAX; }
2558
+ else {
2559
+ unsigned int min_lane;
2560
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(min_vec.ymm, _mm256_set1_epi8((char)min_value_comparable));
2561
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), min_loop_cycle_u8x32,
2562
+ value_match_b8x32);
2563
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
2564
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
2565
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
2566
+ nk_b256_vec_t loop_cycle_vec;
2567
+ loop_cycle_vec.ymm = min_loop_cycle_u8x32;
2568
+ *min_index_ptr = (nk_size_t)loop_cycle_vec.u8s[min_lane] * 32 + min_lane;
2569
+ nk_b256_vec_t min_raw_vec;
2570
+ min_raw_vec.ymm = nk_u8x32_comparable_to_fp8x32_haswell_(min_vec.ymm);
2571
+ *min_value_ptr = min_raw_vec.e4m3s[min_lane];
2572
+ }
2573
+ if (max_value_comparable == 0x00) { *max_value_ptr = NK_E4M3_MIN, *max_index_ptr = NK_SIZE_MAX; }
2574
+ else {
2575
+ unsigned int max_lane;
2576
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(max_vec.ymm, _mm256_set1_epi8((char)max_value_comparable));
2577
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), max_loop_cycle_u8x32,
2578
+ value_match_b8x32);
2579
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
2580
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
2581
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
2582
+ nk_b256_vec_t loop_cycle_vec;
2583
+ loop_cycle_vec.ymm = max_loop_cycle_u8x32;
2584
+ *max_index_ptr = (nk_size_t)loop_cycle_vec.u8s[max_lane] * 32 + max_lane;
2585
+ nk_b256_vec_t max_raw_vec;
2586
+ max_raw_vec.ymm = nk_u8x32_comparable_to_fp8x32_haswell_(max_vec.ymm);
2587
+ *max_value_ptr = max_raw_vec.e4m3s[max_lane];
2588
+ }
2589
+ }
2590
+
2591
+ NK_PUBLIC void nk_reduce_minmax_e4m3_haswell( //
2592
+ nk_e4m3_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2593
+ nk_e4m3_t *min_value_ptr, nk_size_t *min_index_ptr, //
2594
+ nk_e4m3_t *max_value_ptr, nk_size_t *max_index_ptr) {
2595
+
2596
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e4m3_t);
2597
+ if (count == 0)
2598
+ *min_value_ptr = NK_E4M3_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_E4M3_MIN,
2599
+ *max_index_ptr = NK_SIZE_MAX;
2600
+ else if (stride_elements == 1 && count > (nk_size_t)(NK_U8_MAX + 1) * 32) {
2601
+ nk_size_t left_count = count / 2;
2602
+ nk_e4m3_t left_min, right_min, left_max, right_max;
2603
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
2604
+ nk_reduce_minmax_e4m3_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
2605
+ &left_max_index);
2606
+ nk_reduce_minmax_e4m3_haswell(data_ptr + left_count, count - left_count, stride_bytes, &right_min,
2607
+ &right_min_index, &right_max, &right_max_index);
2608
+ // Prefer the side that found valid data (NK_SIZE_MAX means all-NaN)
2609
+ if (left_min_index == NK_SIZE_MAX)
2610
+ *min_value_ptr = right_min,
2611
+ *min_index_ptr = right_min_index == NK_SIZE_MAX ? NK_SIZE_MAX : left_count + right_min_index;
2612
+ else if (right_min_index == NK_SIZE_MAX || nk_e4m3_order_serial(left_min, right_min) <= 0)
2613
+ *min_value_ptr = left_min, *min_index_ptr = left_min_index;
2614
+ else *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
2615
+ if (left_max_index == NK_SIZE_MAX)
2616
+ *max_value_ptr = right_max,
2617
+ *max_index_ptr = right_max_index == NK_SIZE_MAX ? NK_SIZE_MAX : left_count + right_max_index;
2618
+ else if (right_max_index == NK_SIZE_MAX || nk_e4m3_order_serial(left_max, right_max) >= 0)
2619
+ *max_value_ptr = left_max, *max_index_ptr = left_max_index;
2620
+ else *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
2621
+ }
2622
+ else if (stride_elements == 1)
2623
+ nk_reduce_minmax_e4m3_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
2624
+ max_index_ptr);
2625
+ else
2626
+ nk_reduce_minmax_e4m3_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2627
+ max_index_ptr);
2628
+ }
2629
+
2630
+ NK_INTERNAL void nk_reduce_moments_e5m2_haswell_contiguous_( //
2631
+ nk_e5m2_t const *data_ptr, nk_size_t count, //
2632
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2633
+
2634
+ __m256 sum_f32x8 = _mm256_setzero_ps();
2635
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
2636
+ nk_size_t idx = 0;
2637
+ for (; idx + 8 <= count; idx += 8) {
2638
+ __m256 data_f32x8 = nk_e5m2x8_to_f32x8_haswell_(_mm_loadl_epi64((__m128i const *)(data_ptr + idx)));
2639
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
2640
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
2641
+ }
2642
+ nk_size_t remaining = count - idx;
2643
+ if (remaining > 0) {
2644
+ nk_b256_vec_t vec;
2645
+ nk_partial_load_e5m2x8_to_f32x8_haswell_(data_ptr + idx, &vec, remaining);
2646
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
2647
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
2648
+ }
2649
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
2650
+ }
2651
+
2652
+ NK_INTERNAL void nk_reduce_moments_e5m2_haswell_strided_( //
2653
+ nk_e5m2_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
2654
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2655
+
2656
+ __m256 sum_f32x8 = _mm256_setzero_ps();
2657
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
2658
+ nk_size_t idx = 0;
2659
+ nk_size_t elems_per_chunk = 8;
2660
+ for (; idx + elems_per_chunk <= count; idx += elems_per_chunk) {
2661
+ nk_b64_vec_t buf;
2662
+ buf.u64 = 0;
2663
+ nk_e5m2_t const *ptr = data_ptr + idx * stride_elements;
2664
+ for (nk_size_t i = 0; i < elems_per_chunk; ++i) buf.u8s[i] = ptr[i * stride_elements];
2665
+ __m256 data_f32x8 = nk_e5m2x8_to_f32x8_haswell_(_mm_cvtsi64_si128(buf.u64));
2666
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
2667
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
2668
+ }
2669
+ nk_size_t remaining = count - idx;
2670
+ if (remaining > 0) {
2671
+ nk_b64_vec_t buf;
2672
+ buf.u64 = 0;
2673
+ nk_e5m2_t const *ptr = data_ptr + idx * stride_elements;
2674
+ for (nk_size_t i = 0; i < remaining; ++i) buf.u8s[i] = ptr[i * stride_elements];
2675
+ nk_b256_vec_t vec;
2676
+ nk_partial_load_e5m2x8_to_f32x8_haswell_((nk_e5m2_t *)&buf, &vec, remaining);
2677
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
2678
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
2679
+ }
2680
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
2681
+ }
2682
+
2683
+ NK_PUBLIC void nk_reduce_moments_e5m2_haswell( //
2684
+ nk_e5m2_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2685
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2686
+
2687
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e5m2_t);
2688
+ int aligned = (stride_bytes % sizeof(nk_e5m2_t) == 0);
2689
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
2690
+ else if (!aligned) nk_reduce_moments_e5m2_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2691
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 32) {
2692
+ nk_size_t left_count = count / 2;
2693
+ nk_f32_t left_sum, left_sumsq, right_sum, right_sumsq;
2694
+ nk_reduce_moments_e5m2_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
2695
+ nk_reduce_moments_e5m2_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
2696
+ &right_sum, &right_sumsq);
2697
+ *sum_ptr = left_sum + right_sum, *sumsq_ptr = left_sumsq + right_sumsq;
2698
+ }
2699
+ else if (stride_elements == 1) nk_reduce_moments_e5m2_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
2700
+ else if (stride_elements >= 2 && stride_elements <= 8)
2701
+ nk_reduce_moments_e5m2_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
2702
+ else nk_reduce_moments_e5m2_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2703
+ }
2704
+
2705
+ NK_INTERNAL void nk_reduce_minmax_e5m2_haswell_contiguous_( //
2706
+ nk_e5m2_t const *data_ptr, nk_size_t count, //
2707
+ nk_e5m2_t *min_value_ptr, nk_size_t *min_index_ptr, //
2708
+ nk_e5m2_t *max_value_ptr, nk_size_t *max_index_ptr) {
2709
+
2710
+ // E5M2 NaN in comparable form: 0x00-0x02 (neg NaN) and 0xFD-0xFF (pos NaN).
2711
+ // Infinity (comparable 0x03/0xFC) is NOT NaN and IS included.
2712
+ nk_b256_vec_t min_vec, max_vec;
2713
+ min_vec.ymm = _mm256_set1_epi8((char)0xFF);
2714
+ max_vec.ymm = _mm256_setzero_si256();
2715
+ __m256i min_loop_cycle_u8x32 = _mm256_setzero_si256();
2716
+ __m256i max_loop_cycle_u8x32 = _mm256_setzero_si256();
2717
+ __m256i current_loop_cycle_u8x32 = _mm256_setzero_si256();
2718
+ __m256i one_u8x32 = _mm256_set1_epi8(1);
2719
+ __m256i all_ones_u8x32 = _mm256_set1_epi8((char)0xFF);
2720
+ __m256i low_bound_u8x32 = _mm256_set1_epi8(0x02);
2721
+ __m256i high_bound_u8x32 = _mm256_set1_epi8((char)0xFD);
2722
+
2723
+ nk_size_t idx = 0;
2724
+ for (; idx + 32 <= count; idx += 32) {
2725
+ __m256i data_i8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
2726
+ __m256i data_cmp_u8x32 = nk_fp8x32_to_u8x32_comparable_haswell_(data_i8x32);
2727
+ // Detect NaN: comparable <= 0x02 or comparable >= 0xFD
2728
+ __m256i is_nan_low_u8x32 = _mm256_cmpeq_epi8(_mm256_min_epu8(data_cmp_u8x32, low_bound_u8x32), data_cmp_u8x32);
2729
+ __m256i is_nan_high_u8x32 = _mm256_cmpeq_epi8(_mm256_max_epu8(data_cmp_u8x32, high_bound_u8x32),
2730
+ data_cmp_u8x32);
2731
+ __m256i is_nan_u8x32 = _mm256_or_si256(is_nan_low_u8x32, is_nan_high_u8x32);
2732
+ __m256i data_min_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, all_ones_u8x32, is_nan_u8x32);
2733
+ __m256i data_max_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, _mm256_setzero_si256(), is_nan_u8x32);
2734
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, data_min_u8x32);
2735
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm), all_ones_u8x32);
2736
+ min_vec.ymm = new_min_u8x32;
2737
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
2738
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, data_max_u8x32);
2739
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm), all_ones_u8x32);
2740
+ max_vec.ymm = new_max_u8x32;
2741
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
2742
+ current_loop_cycle_u8x32 = _mm256_add_epi8(current_loop_cycle_u8x32, one_u8x32);
2743
+ }
2744
+
2745
+ nk_size_t remaining = count - idx;
2746
+ if (remaining > 0) {
2747
+ nk_b256_vec_t tail_vec;
2748
+ tail_vec.ymm = _mm256_set1_epi8((char)0xFF);
2749
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
2750
+ for (nk_size_t i = remaining; i < 32; ++i) tail_vec.u8s[i] = 0xFF;
2751
+ __m256i data_cmp_u8x32 = nk_fp8x32_to_u8x32_comparable_haswell_(tail_vec.ymm);
2752
+ __m256i is_nan_low_u8x32 = _mm256_cmpeq_epi8(_mm256_min_epu8(data_cmp_u8x32, low_bound_u8x32), data_cmp_u8x32);
2753
+ __m256i is_nan_high_u8x32 = _mm256_cmpeq_epi8(_mm256_max_epu8(data_cmp_u8x32, high_bound_u8x32),
2754
+ data_cmp_u8x32);
2755
+ __m256i is_nan_u8x32 = _mm256_or_si256(is_nan_low_u8x32, is_nan_high_u8x32);
2756
+ __m256i data_min_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, all_ones_u8x32, is_nan_u8x32);
2757
+ __m256i data_max_u8x32 = _mm256_blendv_epi8(data_cmp_u8x32, _mm256_setzero_si256(), is_nan_u8x32);
2758
+ nk_b256_vec_t min_cmp_vec, max_cmp_vec;
2759
+ min_cmp_vec.ymm = data_min_u8x32;
2760
+ max_cmp_vec.ymm = data_max_u8x32;
2761
+ for (nk_size_t i = remaining; i < 32; ++i) min_cmp_vec.u8s[i] = 0xFF, max_cmp_vec.u8s[i] = 0x00;
2762
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, min_cmp_vec.ymm);
2763
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm), all_ones_u8x32);
2764
+ min_vec.ymm = new_min_u8x32;
2765
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
2766
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, max_cmp_vec.ymm);
2767
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm), all_ones_u8x32);
2768
+ max_vec.ymm = new_max_u8x32;
2769
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
2770
+ }
2771
+
2772
+ nk_u8_t min_value_comparable = nk_reduce_min_u8x32_haswell_(min_vec.ymm);
2773
+ nk_u8_t max_value_comparable = nk_reduce_max_u8x32_haswell_(max_vec.ymm);
2774
+
2775
+ // All-NaN early return: both sentinels unchanged means no valid data was found
2776
+ if (min_value_comparable == 0xFF && max_value_comparable == 0x00) {
2777
+ *min_value_ptr = NK_E5M2_MAX, *min_index_ptr = NK_SIZE_MAX;
2778
+ *max_value_ptr = NK_E5M2_MIN, *max_index_ptr = NK_SIZE_MAX;
2779
+ return;
2780
+ }
2781
+
2782
+ if (min_value_comparable == 0xFF) { *min_value_ptr = NK_E5M2_MAX, *min_index_ptr = NK_SIZE_MAX; }
2783
+ else {
2784
+ unsigned int min_lane;
2785
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(min_vec.ymm, _mm256_set1_epi8((char)min_value_comparable));
2786
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), min_loop_cycle_u8x32,
2787
+ value_match_b8x32);
2788
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
2789
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
2790
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
2791
+ nk_b256_vec_t loop_cycle_vec;
2792
+ loop_cycle_vec.ymm = min_loop_cycle_u8x32;
2793
+ *min_index_ptr = (nk_size_t)loop_cycle_vec.u8s[min_lane] * 32 + min_lane;
2794
+ nk_b256_vec_t min_raw_vec;
2795
+ min_raw_vec.ymm = nk_u8x32_comparable_to_fp8x32_haswell_(min_vec.ymm);
2796
+ *min_value_ptr = min_raw_vec.e5m2s[min_lane];
2797
+ }
2798
+ if (max_value_comparable == 0x00) { *max_value_ptr = NK_E5M2_MIN, *max_index_ptr = NK_SIZE_MAX; }
2799
+ else {
2800
+ unsigned int max_lane;
2801
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(max_vec.ymm, _mm256_set1_epi8((char)max_value_comparable));
2802
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), max_loop_cycle_u8x32,
2803
+ value_match_b8x32);
2804
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
2805
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
2806
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
2807
+ nk_b256_vec_t loop_cycle_vec;
2808
+ loop_cycle_vec.ymm = max_loop_cycle_u8x32;
2809
+ *max_index_ptr = (nk_size_t)loop_cycle_vec.u8s[max_lane] * 32 + max_lane;
2810
+ nk_b256_vec_t max_raw_vec;
2811
+ max_raw_vec.ymm = nk_u8x32_comparable_to_fp8x32_haswell_(max_vec.ymm);
2812
+ *max_value_ptr = max_raw_vec.e5m2s[max_lane];
2813
+ }
2814
+ }
2815
+
2816
+ NK_PUBLIC void nk_reduce_minmax_e5m2_haswell( //
2817
+ nk_e5m2_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2818
+ nk_e5m2_t *min_value_ptr, nk_size_t *min_index_ptr, //
2819
+ nk_e5m2_t *max_value_ptr, nk_size_t *max_index_ptr) {
2820
+
2821
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e5m2_t);
2822
+ if (count == 0)
2823
+ *min_value_ptr = NK_E5M2_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_E5M2_MIN,
2824
+ *max_index_ptr = NK_SIZE_MAX;
2825
+ else if (stride_elements == 1 && count > (nk_size_t)(NK_U8_MAX + 1) * 32) {
2826
+ nk_size_t left_count = count / 2;
2827
+ nk_e5m2_t left_min, right_min, left_max, right_max;
2828
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
2829
+ nk_reduce_minmax_e5m2_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
2830
+ &left_max_index);
2831
+ nk_reduce_minmax_e5m2_haswell(data_ptr + left_count, count - left_count, stride_bytes, &right_min,
2832
+ &right_min_index, &right_max, &right_max_index);
2833
+ if (left_min_index == NK_SIZE_MAX)
2834
+ *min_value_ptr = right_min,
2835
+ *min_index_ptr = right_min_index == NK_SIZE_MAX ? NK_SIZE_MAX : left_count + right_min_index;
2836
+ else if (right_min_index == NK_SIZE_MAX || nk_e5m2_order_serial(left_min, right_min) <= 0)
2837
+ *min_value_ptr = left_min, *min_index_ptr = left_min_index;
2838
+ else *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
2839
+ if (left_max_index == NK_SIZE_MAX)
2840
+ *max_value_ptr = right_max,
2841
+ *max_index_ptr = right_max_index == NK_SIZE_MAX ? NK_SIZE_MAX : left_count + right_max_index;
2842
+ else if (right_max_index == NK_SIZE_MAX || nk_e5m2_order_serial(left_max, right_max) >= 0)
2843
+ *max_value_ptr = left_max, *max_index_ptr = left_max_index;
2844
+ else *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
2845
+ }
2846
+ else if (stride_elements == 1)
2847
+ nk_reduce_minmax_e5m2_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
2848
+ max_index_ptr);
2849
+ else
2850
+ nk_reduce_minmax_e5m2_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
2851
+ max_index_ptr);
2852
+ }
2853
+
2854
+ NK_INTERNAL void nk_reduce_moments_e2m3_haswell_contiguous_( //
2855
+ nk_e2m3_t const *data_ptr, nk_size_t count, //
2856
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2857
+
2858
+ __m256 sum_f32x8 = _mm256_setzero_ps();
2859
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
2860
+ nk_size_t idx = 0;
2861
+ for (; idx + 8 <= count; idx += 8) {
2862
+ __m256 data_f32x8 = nk_e2m3x8_to_f32x8_haswell_(_mm_loadl_epi64((__m128i const *)(data_ptr + idx)));
2863
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
2864
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
2865
+ }
2866
+ nk_size_t remaining = count - idx;
2867
+ if (remaining > 0) {
2868
+ nk_b256_vec_t vec;
2869
+ nk_partial_load_e2m3x8_to_f32x8_haswell_(data_ptr + idx, &vec, remaining);
2870
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
2871
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
2872
+ }
2873
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
2874
+ }
2875
+
2876
+ NK_INTERNAL void nk_reduce_moments_e2m3_haswell_strided_( //
2877
+ nk_e2m3_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
2878
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2879
+
2880
+ __m256 sum_f32x8 = _mm256_setzero_ps();
2881
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
2882
+ nk_size_t idx = 0;
2883
+ for (; idx + 8 <= count; idx += 8) {
2884
+ nk_b64_vec_t buf;
2885
+ buf.u64 = 0;
2886
+ nk_e2m3_t const *ptr = data_ptr + idx * stride_elements;
2887
+ for (nk_size_t i = 0; i < 8; ++i) buf.u8s[i] = ptr[i * stride_elements];
2888
+ __m256 data_f32x8 = nk_e2m3x8_to_f32x8_haswell_(_mm_cvtsi64_si128(buf.u64));
2889
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
2890
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
2891
+ }
2892
+ nk_size_t remaining = count - idx;
2893
+ if (remaining > 0) {
2894
+ nk_b64_vec_t buf;
2895
+ buf.u64 = 0;
2896
+ nk_e2m3_t const *ptr = data_ptr + idx * stride_elements;
2897
+ for (nk_size_t i = 0; i < remaining; ++i) buf.u8s[i] = ptr[i * stride_elements];
2898
+ nk_b256_vec_t vec;
2899
+ nk_partial_load_e2m3x8_to_f32x8_haswell_((nk_e2m3_t *)&buf, &vec, remaining);
2900
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
2901
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
2902
+ }
2903
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
2904
+ }
2905
+
2906
+ NK_PUBLIC void nk_reduce_moments_e2m3_haswell( //
2907
+ nk_e2m3_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
2908
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
2909
+
2910
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e2m3_t);
2911
+ int aligned = (stride_bytes % sizeof(nk_e2m3_t) == 0);
2912
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
2913
+ else if (!aligned) nk_reduce_moments_e2m3_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2914
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 32) {
2915
+ nk_size_t left_count = count / 2;
2916
+ nk_f32_t left_sum, left_sumsq, right_sum, right_sumsq;
2917
+ nk_reduce_moments_e2m3_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
2918
+ nk_reduce_moments_e2m3_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
2919
+ &right_sum, &right_sumsq);
2920
+ *sum_ptr = left_sum + right_sum, *sumsq_ptr = left_sumsq + right_sumsq;
2921
+ }
2922
+ else if (stride_elements == 1) nk_reduce_moments_e2m3_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
2923
+ else if (stride_elements >= 2 && stride_elements <= 8)
2924
+ nk_reduce_moments_e2m3_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
2925
+ else nk_reduce_moments_e2m3_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
2926
+ }
2927
+
2928
+ NK_INTERNAL __m256i nk_fp6x32_to_u8x32_comparable_haswell_(__m256i raw_i8x32) {
2929
+ raw_i8x32 = _mm256_and_si256(raw_i8x32, _mm256_set1_epi8(0x3F)); // mask to 6 valid bits
2930
+ __m256i sign_mask_i8x32 = _mm256_set1_epi8(0x20);
2931
+ __m256i neg_i8x32 = _mm256_cmpeq_epi8(_mm256_and_si256(raw_i8x32, sign_mask_i8x32), sign_mask_i8x32);
2932
+ __m256i pos_xor_i8x32 = sign_mask_i8x32; // flip sign bit only
2933
+ __m256i neg_xor_i8x32 = _mm256_set1_epi8(0x3F); // flip all 6 bits
2934
+ __m256i xor_i8x32 = _mm256_blendv_epi8(pos_xor_i8x32, neg_xor_i8x32, neg_i8x32);
2935
+ return _mm256_xor_si256(raw_i8x32, xor_i8x32);
2936
+ }
2937
+
2938
+ NK_INTERNAL __m256i nk_u8x32_comparable_to_fp6x32_haswell_(__m256i cmp_i8x32) {
2939
+ __m256i sign_mask_i8x32 = _mm256_set1_epi8(0x20);
2940
+ __m256i was_neg_i8x32 = _mm256_cmpeq_epi8(_mm256_and_si256(cmp_i8x32, sign_mask_i8x32), _mm256_setzero_si256());
2941
+ __m256i neg_xor_i8x32 = _mm256_set1_epi8(0x3F);
2942
+ __m256i pos_xor_i8x32 = sign_mask_i8x32;
2943
+ __m256i xor_i8x32 = _mm256_blendv_epi8(pos_xor_i8x32, neg_xor_i8x32, was_neg_i8x32);
2944
+ return _mm256_xor_si256(cmp_i8x32, xor_i8x32);
2945
+ }
2946
+
2947
+ NK_INTERNAL void nk_reduce_minmax_e2m3_haswell_contiguous_( //
2948
+ nk_e2m3_t const *data_ptr, nk_size_t count, //
2949
+ nk_e2m3_t *min_value_ptr, nk_size_t *min_index_ptr, //
2950
+ nk_e2m3_t *max_value_ptr, nk_size_t *max_index_ptr) {
2951
+
2952
+ // FP6 has no NaN — use simple unsigned min/max on comparable form
2953
+ nk_b256_vec_t min_vec, max_vec;
2954
+ min_vec.ymm = _mm256_set1_epi8((char)0xFF);
2955
+ max_vec.ymm = _mm256_setzero_si256();
2956
+ __m256i min_loop_cycle_u8x32 = _mm256_setzero_si256();
2957
+ __m256i max_loop_cycle_u8x32 = _mm256_setzero_si256();
2958
+ __m256i current_loop_cycle_u8x32 = _mm256_setzero_si256();
2959
+ __m256i one_u8x32 = _mm256_set1_epi8(1);
2960
+
2961
+ nk_size_t idx = 0;
2962
+ for (; idx + 32 <= count; idx += 32) {
2963
+ __m256i data_i8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
2964
+ __m256i data_cmp_u8x32 = nk_fp6x32_to_u8x32_comparable_haswell_(data_i8x32);
2965
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, data_cmp_u8x32);
2966
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm),
2967
+ _mm256_set1_epi8((char)0xFF));
2968
+ min_vec.ymm = new_min_u8x32;
2969
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
2970
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, data_cmp_u8x32);
2971
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm),
2972
+ _mm256_set1_epi8((char)0xFF));
2973
+ max_vec.ymm = new_max_u8x32;
2974
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
2975
+ current_loop_cycle_u8x32 = _mm256_add_epi8(current_loop_cycle_u8x32, one_u8x32);
2976
+ }
2977
+
2978
+ nk_size_t remaining = count - idx;
2979
+ if (remaining > 0) {
2980
+ nk_b256_vec_t tail_vec;
2981
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
2982
+ __m256i data_cmp_u8x32 = nk_fp6x32_to_u8x32_comparable_haswell_(tail_vec.ymm);
2983
+ // Fill invalid lanes with identity: 0x3F for min (max comparable), 0x00 for max (min comparable)
2984
+ __m256i lane_indices_u8x32 = _mm256_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
2985
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
2986
+ __m256i valid_b8x32 = _mm256_cmpgt_epi8(_mm256_set1_epi8((char)remaining), lane_indices_u8x32);
2987
+ __m256i data_min_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8(0x3F), data_cmp_u8x32, valid_b8x32);
2988
+ __m256i data_max_u8x32 = _mm256_blendv_epi8(_mm256_setzero_si256(), data_cmp_u8x32, valid_b8x32);
2989
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, data_min_u8x32);
2990
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm),
2991
+ _mm256_set1_epi8((char)0xFF));
2992
+ min_vec.ymm = new_min_u8x32;
2993
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
2994
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, data_max_u8x32);
2995
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm),
2996
+ _mm256_set1_epi8((char)0xFF));
2997
+ max_vec.ymm = new_max_u8x32;
2998
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
2999
+ }
3000
+
3001
+ nk_u8_t min_value_comparable = nk_reduce_min_u8x32_haswell_(min_vec.ymm);
3002
+ nk_u8_t max_value_comparable = nk_reduce_max_u8x32_haswell_(max_vec.ymm);
3003
+ unsigned int min_lane, max_lane;
3004
+ {
3005
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(min_vec.ymm, _mm256_set1_epi8((char)min_value_comparable));
3006
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), min_loop_cycle_u8x32,
3007
+ value_match_b8x32);
3008
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
3009
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
3010
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
3011
+ }
3012
+ {
3013
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(max_vec.ymm, _mm256_set1_epi8((char)max_value_comparable));
3014
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), max_loop_cycle_u8x32,
3015
+ value_match_b8x32);
3016
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
3017
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
3018
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
3019
+ }
3020
+ nk_b256_vec_t loop_cycle_vec;
3021
+ loop_cycle_vec.ymm = min_loop_cycle_u8x32;
3022
+ *min_index_ptr = (nk_size_t)loop_cycle_vec.u8s[min_lane] * 32 + min_lane;
3023
+ loop_cycle_vec.ymm = max_loop_cycle_u8x32;
3024
+ *max_index_ptr = (nk_size_t)loop_cycle_vec.u8s[max_lane] * 32 + max_lane;
3025
+ min_vec.ymm = nk_u8x32_comparable_to_fp6x32_haswell_(min_vec.ymm);
3026
+ max_vec.ymm = nk_u8x32_comparable_to_fp6x32_haswell_(max_vec.ymm);
3027
+ *min_value_ptr = min_vec.e2m3s[min_lane];
3028
+ *max_value_ptr = max_vec.e2m3s[max_lane];
3029
+ }
3030
+
3031
+ NK_PUBLIC void nk_reduce_minmax_e2m3_haswell( //
3032
+ nk_e2m3_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3033
+ nk_e2m3_t *min_value_ptr, nk_size_t *min_index_ptr, //
3034
+ nk_e2m3_t *max_value_ptr, nk_size_t *max_index_ptr) {
3035
+
3036
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e2m3_t);
3037
+ if (count == 0)
3038
+ *min_value_ptr = NK_E2M3_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_E2M3_MIN,
3039
+ *max_index_ptr = NK_SIZE_MAX;
3040
+ else if (stride_elements == 1 && count > (nk_size_t)(NK_U8_MAX + 1) * 32) {
3041
+ nk_size_t left_count = count / 2;
3042
+ nk_e2m3_t left_min, right_min, left_max, right_max;
3043
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
3044
+ nk_reduce_minmax_e2m3_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
3045
+ &left_max_index);
3046
+ nk_reduce_minmax_e2m3_haswell(data_ptr + left_count, count - left_count, stride_bytes, &right_min,
3047
+ &right_min_index, &right_max, &right_max_index);
3048
+ if (nk_e2m3_order_serial(right_min, left_min) < 0)
3049
+ *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
3050
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
3051
+ if (nk_e2m3_order_serial(right_max, left_max) > 0)
3052
+ *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
3053
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
3054
+ }
3055
+ else if (stride_elements == 1)
3056
+ nk_reduce_minmax_e2m3_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
3057
+ max_index_ptr);
3058
+ else
3059
+ nk_reduce_minmax_e2m3_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
3060
+ max_index_ptr);
3061
+ }
3062
+
3063
+ NK_INTERNAL void nk_reduce_moments_e3m2_haswell_contiguous_( //
3064
+ nk_e3m2_t const *data_ptr, nk_size_t count, //
3065
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
3066
+
3067
+ __m256 sum_f32x8 = _mm256_setzero_ps();
3068
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
3069
+ nk_size_t idx = 0;
3070
+ for (; idx + 8 <= count; idx += 8) {
3071
+ __m256 data_f32x8 = nk_e3m2x8_to_f32x8_haswell_(_mm_loadl_epi64((__m128i const *)(data_ptr + idx)));
3072
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
3073
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
3074
+ }
3075
+ nk_size_t remaining = count - idx;
3076
+ if (remaining > 0) {
3077
+ nk_b256_vec_t vec;
3078
+ nk_partial_load_e3m2x8_to_f32x8_haswell_(data_ptr + idx, &vec, remaining);
3079
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
3080
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
3081
+ }
3082
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
3083
+ }
3084
+
3085
+ NK_INTERNAL void nk_reduce_moments_e3m2_haswell_strided_( //
3086
+ nk_e3m2_t const *data_ptr, nk_size_t count, nk_size_t stride_elements, //
3087
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
3088
+
3089
+ __m256 sum_f32x8 = _mm256_setzero_ps();
3090
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
3091
+ nk_size_t idx = 0;
3092
+ for (; idx + 8 <= count; idx += 8) {
3093
+ nk_b64_vec_t buf;
3094
+ buf.u64 = 0;
3095
+ nk_e3m2_t const *ptr = data_ptr + idx * stride_elements;
3096
+ for (nk_size_t i = 0; i < 8; ++i) buf.u8s[i] = ptr[i * stride_elements];
3097
+ __m256 data_f32x8 = nk_e3m2x8_to_f32x8_haswell_(_mm_cvtsi64_si128(buf.u64));
3098
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, data_f32x8);
3099
+ sumsq_f32x8 = _mm256_fmadd_ps(data_f32x8, data_f32x8, sumsq_f32x8);
3100
+ }
3101
+ nk_size_t remaining = count - idx;
3102
+ if (remaining > 0) {
3103
+ nk_b64_vec_t buf;
3104
+ buf.u64 = 0;
3105
+ nk_e3m2_t const *ptr = data_ptr + idx * stride_elements;
3106
+ for (nk_size_t i = 0; i < remaining; ++i) buf.u8s[i] = ptr[i * stride_elements];
3107
+ nk_b256_vec_t vec;
3108
+ nk_partial_load_e3m2x8_to_f32x8_haswell_((nk_e3m2_t *)&buf, &vec, remaining);
3109
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, vec.ymm_ps);
3110
+ sumsq_f32x8 = _mm256_fmadd_ps(vec.ymm_ps, vec.ymm_ps, sumsq_f32x8);
3111
+ }
3112
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
3113
+ }
3114
+
3115
+ NK_PUBLIC void nk_reduce_moments_e3m2_haswell( //
3116
+ nk_e3m2_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3117
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
3118
+
3119
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e3m2_t);
3120
+ int aligned = (stride_bytes % sizeof(nk_e3m2_t) == 0);
3121
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
3122
+ else if (!aligned) nk_reduce_moments_e3m2_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3123
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 32) {
3124
+ nk_size_t left_count = count / 2;
3125
+ nk_f32_t left_sum, left_sumsq, right_sum, right_sumsq;
3126
+ nk_reduce_moments_e3m2_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
3127
+ nk_reduce_moments_e3m2_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
3128
+ &right_sum, &right_sumsq);
3129
+ *sum_ptr = left_sum + right_sum, *sumsq_ptr = left_sumsq + right_sumsq;
3130
+ }
3131
+ else if (stride_elements == 1) nk_reduce_moments_e3m2_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
3132
+ else if (stride_elements >= 2 && stride_elements <= 8)
3133
+ nk_reduce_moments_e3m2_haswell_strided_(data_ptr, count, stride_elements, sum_ptr, sumsq_ptr);
3134
+ else nk_reduce_moments_e3m2_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3135
+ }
3136
+
3137
+ NK_INTERNAL void nk_reduce_minmax_e3m2_haswell_contiguous_( //
3138
+ nk_e3m2_t const *data_ptr, nk_size_t count, //
3139
+ nk_e3m2_t *min_value_ptr, nk_size_t *min_index_ptr, //
3140
+ nk_e3m2_t *max_value_ptr, nk_size_t *max_index_ptr) {
3141
+
3142
+ nk_b256_vec_t min_vec, max_vec;
3143
+ min_vec.ymm = _mm256_set1_epi8((char)0xFF);
3144
+ max_vec.ymm = _mm256_setzero_si256();
3145
+ __m256i min_loop_cycle_u8x32 = _mm256_setzero_si256();
3146
+ __m256i max_loop_cycle_u8x32 = _mm256_setzero_si256();
3147
+ __m256i current_loop_cycle_u8x32 = _mm256_setzero_si256();
3148
+ __m256i one_u8x32 = _mm256_set1_epi8(1);
3149
+
3150
+ nk_size_t idx = 0;
3151
+ for (; idx + 32 <= count; idx += 32) {
3152
+ __m256i data_i8x32 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
3153
+ __m256i data_cmp_u8x32 = nk_fp6x32_to_u8x32_comparable_haswell_(data_i8x32);
3154
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, data_cmp_u8x32);
3155
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm),
3156
+ _mm256_set1_epi8((char)0xFF));
3157
+ min_vec.ymm = new_min_u8x32;
3158
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
3159
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, data_cmp_u8x32);
3160
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm),
3161
+ _mm256_set1_epi8((char)0xFF));
3162
+ max_vec.ymm = new_max_u8x32;
3163
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
3164
+ current_loop_cycle_u8x32 = _mm256_add_epi8(current_loop_cycle_u8x32, one_u8x32);
3165
+ }
3166
+
3167
+ nk_size_t remaining = count - idx;
3168
+ if (remaining > 0) {
3169
+ nk_b256_vec_t tail_vec;
3170
+ nk_partial_load_b8x32_serial_(data_ptr + idx, &tail_vec, remaining);
3171
+ __m256i data_cmp_u8x32 = nk_fp6x32_to_u8x32_comparable_haswell_(tail_vec.ymm);
3172
+ __m256i lane_indices_u8x32 = _mm256_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
3173
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
3174
+ __m256i valid_b8x32 = _mm256_cmpgt_epi8(_mm256_set1_epi8((char)remaining), lane_indices_u8x32);
3175
+ __m256i data_min_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8(0x3F), data_cmp_u8x32, valid_b8x32);
3176
+ __m256i data_max_u8x32 = _mm256_blendv_epi8(_mm256_setzero_si256(), data_cmp_u8x32, valid_b8x32);
3177
+ __m256i new_min_u8x32 = _mm256_min_epu8(min_vec.ymm, data_min_u8x32);
3178
+ __m256i min_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_min_u8x32, min_vec.ymm),
3179
+ _mm256_set1_epi8((char)0xFF));
3180
+ min_vec.ymm = new_min_u8x32;
3181
+ min_loop_cycle_u8x32 = _mm256_blendv_epi8(min_loop_cycle_u8x32, current_loop_cycle_u8x32, min_changed_i8x32);
3182
+ __m256i new_max_u8x32 = _mm256_max_epu8(max_vec.ymm, data_max_u8x32);
3183
+ __m256i max_changed_i8x32 = _mm256_xor_si256(_mm256_cmpeq_epi8(new_max_u8x32, max_vec.ymm),
3184
+ _mm256_set1_epi8((char)0xFF));
3185
+ max_vec.ymm = new_max_u8x32;
3186
+ max_loop_cycle_u8x32 = _mm256_blendv_epi8(max_loop_cycle_u8x32, current_loop_cycle_u8x32, max_changed_i8x32);
3187
+ }
3188
+
3189
+ nk_u8_t min_value_comparable = nk_reduce_min_u8x32_haswell_(min_vec.ymm);
3190
+ nk_u8_t max_value_comparable = nk_reduce_max_u8x32_haswell_(max_vec.ymm);
3191
+ unsigned int min_lane, max_lane;
3192
+ {
3193
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(min_vec.ymm, _mm256_set1_epi8((char)min_value_comparable));
3194
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), min_loop_cycle_u8x32,
3195
+ value_match_b8x32);
3196
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
3197
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
3198
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
3199
+ }
3200
+ {
3201
+ __m256i value_match_b8x32 = _mm256_cmpeq_epi8(max_vec.ymm, _mm256_set1_epi8((char)max_value_comparable));
3202
+ __m256i masked_cycle_u8x32 = _mm256_blendv_epi8(_mm256_set1_epi8((char)NK_U8_MAX), max_loop_cycle_u8x32,
3203
+ value_match_b8x32);
3204
+ nk_u8_t earliest_loop_cycle = nk_reduce_min_u8x32_haswell_(masked_cycle_u8x32);
3205
+ __m256i cycle_match_b8x32 = _mm256_cmpeq_epi8(masked_cycle_u8x32, _mm256_set1_epi8((char)earliest_loop_cycle));
3206
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b8x32));
3207
+ }
3208
+ nk_b256_vec_t loop_cycle_vec;
3209
+ loop_cycle_vec.ymm = min_loop_cycle_u8x32;
3210
+ *min_index_ptr = (nk_size_t)loop_cycle_vec.u8s[min_lane] * 32 + min_lane;
3211
+ loop_cycle_vec.ymm = max_loop_cycle_u8x32;
3212
+ *max_index_ptr = (nk_size_t)loop_cycle_vec.u8s[max_lane] * 32 + max_lane;
3213
+ min_vec.ymm = nk_u8x32_comparable_to_fp6x32_haswell_(min_vec.ymm);
3214
+ max_vec.ymm = nk_u8x32_comparable_to_fp6x32_haswell_(max_vec.ymm);
3215
+ *min_value_ptr = min_vec.e3m2s[min_lane];
3216
+ *max_value_ptr = max_vec.e3m2s[max_lane];
3217
+ }
3218
+
3219
+ NK_PUBLIC void nk_reduce_minmax_e3m2_haswell( //
3220
+ nk_e3m2_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3221
+ nk_e3m2_t *min_value_ptr, nk_size_t *min_index_ptr, //
3222
+ nk_e3m2_t *max_value_ptr, nk_size_t *max_index_ptr) {
3223
+
3224
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_e3m2_t);
3225
+ if (count == 0)
3226
+ *min_value_ptr = NK_E3M2_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_E3M2_MIN,
3227
+ *max_index_ptr = NK_SIZE_MAX;
3228
+ else if (stride_elements == 1 && count > (nk_size_t)(NK_U8_MAX + 1) * 32) {
3229
+ nk_size_t left_count = count / 2;
3230
+ nk_e3m2_t left_min, right_min, left_max, right_max;
3231
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
3232
+ nk_reduce_minmax_e3m2_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
3233
+ &left_max_index);
3234
+ nk_reduce_minmax_e3m2_haswell(data_ptr + left_count, count - left_count, stride_bytes, &right_min,
3235
+ &right_min_index, &right_max, &right_max_index);
3236
+ if (nk_e3m2_order_serial(right_min, left_min) < 0)
3237
+ *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
3238
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
3239
+ if (nk_e3m2_order_serial(right_max, left_max) > 0)
3240
+ *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
3241
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
3242
+ }
3243
+ else if (stride_elements == 1)
3244
+ nk_reduce_minmax_e3m2_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
3245
+ max_index_ptr);
3246
+ else
3247
+ nk_reduce_minmax_e3m2_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
3248
+ max_index_ptr);
3249
+ }
3250
+
3251
+ NK_INTERNAL void nk_reduce_moments_bf16_haswell_contiguous_( //
3252
+ nk_bf16_t const *data_ptr, nk_size_t count, //
3253
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
3254
+
3255
+ __m256 sum_f32x8 = _mm256_setzero_ps();
3256
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
3257
+ nk_size_t idx = 0;
3258
+ for (; idx + 16 <= count; idx += 16) {
3259
+ __m256 low_f32x8 = _mm256_castsi256_ps(
3260
+ _mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i const *)(data_ptr + idx))), 16));
3261
+ __m256 high_f32x8 = _mm256_castsi256_ps(
3262
+ _mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i const *)(data_ptr + idx + 8))), 16));
3263
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, low_f32x8);
3264
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, high_f32x8);
3265
+ sumsq_f32x8 = _mm256_fmadd_ps(low_f32x8, low_f32x8, sumsq_f32x8);
3266
+ sumsq_f32x8 = _mm256_fmadd_ps(high_f32x8, high_f32x8, sumsq_f32x8);
3267
+ }
3268
+ nk_size_t remaining = count - idx;
3269
+ if (remaining > 0) {
3270
+ nk_b256_vec_t partial_vec;
3271
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &partial_vec, remaining);
3272
+ nk_size_t first_half = remaining > 8 ? 8 : remaining;
3273
+ nk_unused_(first_half);
3274
+ __m256 low_f32x8 = _mm256_castsi256_ps(
3275
+ _mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm256_castsi256_si128(partial_vec.ymm)), 16));
3276
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, low_f32x8);
3277
+ sumsq_f32x8 = _mm256_fmadd_ps(low_f32x8, low_f32x8, sumsq_f32x8);
3278
+ if (remaining > 8) {
3279
+ __m256 high_f32x8 = _mm256_castsi256_ps(
3280
+ _mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm256_extracti128_si256(partial_vec.ymm, 1)), 16));
3281
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, high_f32x8);
3282
+ sumsq_f32x8 = _mm256_fmadd_ps(high_f32x8, high_f32x8, sumsq_f32x8);
3283
+ }
3284
+ }
3285
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
3286
+ }
3287
+
3288
+ NK_PUBLIC void nk_reduce_moments_bf16_haswell( //
3289
+ nk_bf16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3290
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
3291
+
3292
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_bf16_t);
3293
+ int aligned = (stride_bytes % sizeof(nk_bf16_t) == 0);
3294
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
3295
+ else if (!aligned) nk_reduce_moments_bf16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3296
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 16) {
3297
+ nk_size_t left_count = count / 2;
3298
+ nk_f32_t left_sum, left_sumsq, right_sum, right_sumsq;
3299
+ nk_reduce_moments_bf16_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
3300
+ nk_reduce_moments_bf16_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
3301
+ &right_sum, &right_sumsq);
3302
+ *sum_ptr = left_sum + right_sum;
3303
+ *sumsq_ptr = left_sumsq + right_sumsq;
3304
+ }
3305
+ else if (stride_elements == 1) nk_reduce_moments_bf16_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
3306
+ else nk_reduce_moments_bf16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3307
+ }
3308
+
3309
+ NK_INTERNAL void nk_reduce_minmax_bf16_haswell_contiguous_( //
3310
+ nk_bf16_t const *data_ptr, nk_size_t count, //
3311
+ nk_bf16_t *min_value_ptr, nk_size_t *min_index_ptr, //
3312
+ nk_bf16_t *max_value_ptr, nk_size_t *max_index_ptr) {
3313
+
3314
+ __m256i abs_mask_u16x16 = _mm256_set1_epi16(0x7FFF);
3315
+ __m256i nan_threshold_u16x16 = _mm256_set1_epi16((short)0x7F80);
3316
+ __m256i all_ones_i16x16 = _mm256_set1_epi8((char)0xFF);
3317
+ __m256i min_cmp_i16x16 = _mm256_set1_epi16((short)0x7FFF);
3318
+ __m256i max_cmp_i16x16 = _mm256_set1_epi16((short)0x8000);
3319
+ __m256i min_loop_cycle_u16x16 = _mm256_setzero_si256();
3320
+ __m256i max_loop_cycle_u16x16 = _mm256_setzero_si256();
3321
+ __m256i current_loop_cycle_u16x16 = _mm256_setzero_si256();
3322
+ __m256i one_u16x16 = _mm256_set1_epi16(1);
3323
+
3324
+ nk_size_t idx = 0;
3325
+ for (; idx + 16 <= count; idx += 16) {
3326
+ __m256i raw_u16x16 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
3327
+ __m256i data_cmp_i16x16 = nk_bf16x16_to_comparable_i16x16_haswell_(raw_u16x16);
3328
+ __m256i abs_u16x16 = _mm256_and_si256(raw_u16x16, abs_mask_u16x16);
3329
+ __m256i nan_detect_i16x16 = _mm256_cmpgt_epi16(abs_u16x16, nan_threshold_u16x16);
3330
+ __m256i not_nan_i16x16 = _mm256_xor_si256(nan_detect_i16x16, all_ones_i16x16);
3331
+ __m256i less_i16x16 = _mm256_cmpgt_epi16(min_cmp_i16x16, data_cmp_i16x16);
3332
+ __m256i min_changed_i16x16 = _mm256_and_si256(less_i16x16, not_nan_i16x16);
3333
+ min_cmp_i16x16 = _mm256_blendv_epi8(min_cmp_i16x16, data_cmp_i16x16, min_changed_i16x16);
3334
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16,
3335
+ min_changed_i16x16);
3336
+ __m256i greater_i16x16 = _mm256_cmpgt_epi16(data_cmp_i16x16, max_cmp_i16x16);
3337
+ __m256i max_changed_i16x16 = _mm256_and_si256(greater_i16x16, not_nan_i16x16);
3338
+ max_cmp_i16x16 = _mm256_blendv_epi8(max_cmp_i16x16, data_cmp_i16x16, max_changed_i16x16);
3339
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16,
3340
+ max_changed_i16x16);
3341
+ current_loop_cycle_u16x16 = _mm256_add_epi16(current_loop_cycle_u16x16, one_u16x16);
3342
+ }
3343
+
3344
+ nk_size_t remaining = count - idx;
3345
+ if (remaining > 0) {
3346
+ nk_b256_vec_t partial_vec;
3347
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &partial_vec, remaining);
3348
+ __m256i raw_u16x16 = partial_vec.ymm;
3349
+ __m256i data_cmp_i16x16 = nk_bf16x16_to_comparable_i16x16_haswell_(raw_u16x16);
3350
+ __m256i abs_u16x16 = _mm256_and_si256(raw_u16x16, abs_mask_u16x16);
3351
+ __m256i nan_detect_i16x16 = _mm256_cmpgt_epi16(abs_u16x16, nan_threshold_u16x16);
3352
+ __m256i lane_indices_u16x16 = _mm256_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
3353
+ __m256i valid_i16x16 = _mm256_cmpgt_epi16(_mm256_set1_epi16((short)remaining), lane_indices_u16x16);
3354
+ __m256i not_nan_valid_i16x16 = _mm256_andnot_si256(nan_detect_i16x16, valid_i16x16);
3355
+ __m256i less_i16x16 = _mm256_cmpgt_epi16(min_cmp_i16x16, data_cmp_i16x16);
3356
+ __m256i min_changed_i16x16 = _mm256_and_si256(less_i16x16, not_nan_valid_i16x16);
3357
+ min_cmp_i16x16 = _mm256_blendv_epi8(min_cmp_i16x16, data_cmp_i16x16, min_changed_i16x16);
3358
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16,
3359
+ min_changed_i16x16);
3360
+ __m256i greater_i16x16 = _mm256_cmpgt_epi16(data_cmp_i16x16, max_cmp_i16x16);
3361
+ __m256i max_changed_i16x16 = _mm256_and_si256(greater_i16x16, not_nan_valid_i16x16);
3362
+ max_cmp_i16x16 = _mm256_blendv_epi8(max_cmp_i16x16, data_cmp_i16x16, max_changed_i16x16);
3363
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16,
3364
+ max_changed_i16x16);
3365
+ }
3366
+
3367
+ nk_i16_t min_value_comparable = nk_reduce_min_i16x16_haswell_(min_cmp_i16x16);
3368
+ nk_i16_t max_value_comparable = nk_reduce_max_i16x16_haswell_(max_cmp_i16x16);
3369
+ if (min_value_comparable == 0x7FFF && max_value_comparable == (nk_i16_t)0x8000) {
3370
+ *min_value_ptr = NK_BF16_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_BF16_MIN,
3371
+ *max_index_ptr = NK_SIZE_MAX;
3372
+ return;
3373
+ }
3374
+ unsigned int min_lane, max_lane;
3375
+ {
3376
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(min_cmp_i16x16, _mm256_set1_epi16(min_value_comparable));
3377
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), min_loop_cycle_u16x16,
3378
+ value_match_b16x16);
3379
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
3380
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
3381
+ _mm256_set1_epi16((short)earliest_loop_cycle));
3382
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
3383
+ }
3384
+ {
3385
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(max_cmp_i16x16, _mm256_set1_epi16(max_value_comparable));
3386
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), max_loop_cycle_u16x16,
3387
+ value_match_b16x16);
3388
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
3389
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
3390
+ _mm256_set1_epi16((short)earliest_loop_cycle));
3391
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
3392
+ }
3393
+ nk_b256_vec_t loop_cycle_vec;
3394
+ loop_cycle_vec.ymm = min_loop_cycle_u16x16;
3395
+ *min_index_ptr = (nk_size_t)loop_cycle_vec.u16s[min_lane] * 16 + min_lane;
3396
+ loop_cycle_vec.ymm = max_loop_cycle_u16x16;
3397
+ *max_index_ptr = (nk_size_t)loop_cycle_vec.u16s[max_lane] * 16 + max_lane;
3398
+ nk_i16_t min_sign = min_value_comparable >> 15;
3399
+ *min_value_ptr = (nk_bf16_t)((nk_u16_t)min_value_comparable ^ ((nk_u16_t)min_sign >> 1));
3400
+ nk_i16_t max_sign = max_value_comparable >> 15;
3401
+ *max_value_ptr = (nk_bf16_t)((nk_u16_t)max_value_comparable ^ ((nk_u16_t)max_sign >> 1));
3402
+ }
3403
+
3404
+ NK_PUBLIC void nk_reduce_minmax_bf16_haswell( //
3405
+ nk_bf16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3406
+ nk_bf16_t *min_value_ptr, nk_size_t *min_index_ptr, //
3407
+ nk_bf16_t *max_value_ptr, nk_size_t *max_index_ptr) {
3408
+
3409
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_bf16_t);
3410
+ int aligned = (stride_bytes % sizeof(nk_bf16_t) == 0);
3411
+ if (count == 0)
3412
+ *min_value_ptr = NK_BF16_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_BF16_MIN,
3413
+ *max_index_ptr = NK_SIZE_MAX;
3414
+ else if (!aligned)
3415
+ nk_reduce_minmax_bf16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
3416
+ max_index_ptr);
3417
+ else if (stride_elements == 1 && count > (nk_size_t)(NK_U16_MAX + 1) * 16) {
3418
+ nk_size_t left_count = count / 2;
3419
+ nk_bf16_t left_min, right_min, left_max, right_max;
3420
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
3421
+ nk_reduce_minmax_bf16_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
3422
+ &left_max_index);
3423
+ nk_reduce_minmax_bf16_haswell(data_ptr + left_count, count - left_count, stride_bytes, &right_min,
3424
+ &right_min_index, &right_max, &right_max_index);
3425
+ if (nk_bf16_order_serial(right_min, left_min) < 0)
3426
+ *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
3427
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
3428
+ if (nk_bf16_order_serial(right_max, left_max) > 0)
3429
+ *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
3430
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
3431
+ }
3432
+ else if (stride_elements == 1)
3433
+ nk_reduce_minmax_bf16_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
3434
+ max_index_ptr);
3435
+ else
3436
+ nk_reduce_minmax_bf16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
3437
+ max_index_ptr);
3438
+ }
3439
+
3440
+ NK_INTERNAL void nk_reduce_moments_f16_haswell_contiguous_( //
3441
+ nk_f16_t const *data_ptr, nk_size_t count, //
3442
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
3443
+
3444
+ __m256 sum_f32x8 = _mm256_setzero_ps();
3445
+ __m256 sumsq_f32x8 = _mm256_setzero_ps();
3446
+ nk_size_t idx = 0;
3447
+ for (; idx + 16 <= count; idx += 16) {
3448
+ __m256 low_f32x8 = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const *)(data_ptr + idx)));
3449
+ __m256 high_f32x8 = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const *)(data_ptr + idx + 8)));
3450
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, low_f32x8);
3451
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, high_f32x8);
3452
+ sumsq_f32x8 = _mm256_fmadd_ps(low_f32x8, low_f32x8, sumsq_f32x8);
3453
+ sumsq_f32x8 = _mm256_fmadd_ps(high_f32x8, high_f32x8, sumsq_f32x8);
3454
+ }
3455
+ nk_size_t remaining = count - idx;
3456
+ if (remaining > 0) {
3457
+ nk_b256_vec_t partial_vec;
3458
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &partial_vec, remaining);
3459
+ __m256 low_f32x8 = _mm256_cvtph_ps(_mm256_castsi256_si128(partial_vec.ymm));
3460
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, low_f32x8);
3461
+ sumsq_f32x8 = _mm256_fmadd_ps(low_f32x8, low_f32x8, sumsq_f32x8);
3462
+ if (remaining > 8) {
3463
+ __m256 high_f32x8 = _mm256_cvtph_ps(_mm256_extracti128_si256(partial_vec.ymm, 1));
3464
+ sum_f32x8 = _mm256_add_ps(sum_f32x8, high_f32x8);
3465
+ sumsq_f32x8 = _mm256_fmadd_ps(high_f32x8, high_f32x8, sumsq_f32x8);
3466
+ }
3467
+ }
3468
+ *sum_ptr = nk_reduce_add_f32x8_haswell_(sum_f32x8), *sumsq_ptr = nk_reduce_add_f32x8_haswell_(sumsq_f32x8);
3469
+ }
3470
+
3471
+ NK_PUBLIC void nk_reduce_moments_f16_haswell( //
3472
+ nk_f16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3473
+ nk_f32_t *sum_ptr, nk_f32_t *sumsq_ptr) {
3474
+
3475
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_f16_t);
3476
+ int aligned = (stride_bytes % sizeof(nk_f16_t) == 0);
3477
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
3478
+ else if (!aligned) nk_reduce_moments_f16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3479
+ else if (count > (nk_size_t)(NK_U16_MAX + 1) * 16) {
3480
+ nk_size_t left_count = count / 2;
3481
+ nk_f32_t left_sum, left_sumsq, right_sum, right_sumsq;
3482
+ nk_reduce_moments_f16_haswell(data_ptr, left_count, stride_bytes, &left_sum, &left_sumsq);
3483
+ nk_reduce_moments_f16_haswell(data_ptr + left_count * stride_elements, count - left_count, stride_bytes,
3484
+ &right_sum, &right_sumsq);
3485
+ *sum_ptr = left_sum + right_sum;
3486
+ *sumsq_ptr = left_sumsq + right_sumsq;
3487
+ }
3488
+ else if (stride_elements == 1) nk_reduce_moments_f16_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
3489
+ else nk_reduce_moments_f16_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3490
+ }
3491
+
3492
+ NK_INTERNAL void nk_reduce_minmax_f16_haswell_contiguous_( //
3493
+ nk_f16_t const *data_ptr, nk_size_t count, //
3494
+ nk_f16_t *min_value_ptr, nk_size_t *min_index_ptr, //
3495
+ nk_f16_t *max_value_ptr, nk_size_t *max_index_ptr) {
3496
+
3497
+ __m256i abs_mask_u16x16 = _mm256_set1_epi16(0x7FFF);
3498
+ __m256i nan_threshold_u16x16 = _mm256_set1_epi16((short)0x7C00);
3499
+ __m256i all_ones_i16x16 = _mm256_set1_epi8((char)0xFF);
3500
+ __m256i min_cmp_i16x16 = _mm256_set1_epi16((short)0x7FFF);
3501
+ __m256i max_cmp_i16x16 = _mm256_set1_epi16((short)0x8000);
3502
+ __m256i min_loop_cycle_u16x16 = _mm256_setzero_si256();
3503
+ __m256i max_loop_cycle_u16x16 = _mm256_setzero_si256();
3504
+ __m256i current_loop_cycle_u16x16 = _mm256_setzero_si256();
3505
+ __m256i one_u16x16 = _mm256_set1_epi16(1);
3506
+
3507
+ nk_size_t idx = 0;
3508
+ for (; idx + 16 <= count; idx += 16) {
3509
+ __m256i raw_u16x16 = _mm256_loadu_si256((__m256i const *)(data_ptr + idx));
3510
+ __m256i data_cmp_i16x16 = nk_f16x16_to_comparable_i16x16_haswell_(raw_u16x16);
3511
+ __m256i abs_u16x16 = _mm256_and_si256(raw_u16x16, abs_mask_u16x16);
3512
+ __m256i nan_detect_i16x16 = _mm256_cmpgt_epi16(abs_u16x16, nan_threshold_u16x16);
3513
+ __m256i not_nan_i16x16 = _mm256_xor_si256(nan_detect_i16x16, all_ones_i16x16);
3514
+ __m256i less_i16x16 = _mm256_cmpgt_epi16(min_cmp_i16x16, data_cmp_i16x16);
3515
+ __m256i min_changed_i16x16 = _mm256_and_si256(less_i16x16, not_nan_i16x16);
3516
+ min_cmp_i16x16 = _mm256_blendv_epi8(min_cmp_i16x16, data_cmp_i16x16, min_changed_i16x16);
3517
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16,
3518
+ min_changed_i16x16);
3519
+ __m256i greater_i16x16 = _mm256_cmpgt_epi16(data_cmp_i16x16, max_cmp_i16x16);
3520
+ __m256i max_changed_i16x16 = _mm256_and_si256(greater_i16x16, not_nan_i16x16);
3521
+ max_cmp_i16x16 = _mm256_blendv_epi8(max_cmp_i16x16, data_cmp_i16x16, max_changed_i16x16);
3522
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16,
3523
+ max_changed_i16x16);
3524
+ current_loop_cycle_u16x16 = _mm256_add_epi16(current_loop_cycle_u16x16, one_u16x16);
3525
+ }
3526
+
3527
+ nk_size_t remaining = count - idx;
3528
+ if (remaining > 0) {
3529
+ nk_b256_vec_t partial_vec;
3530
+ nk_partial_load_b16x16_serial_(data_ptr + idx, &partial_vec, remaining);
3531
+ __m256i raw_u16x16 = partial_vec.ymm;
3532
+ __m256i data_cmp_i16x16 = nk_f16x16_to_comparable_i16x16_haswell_(raw_u16x16);
3533
+ __m256i abs_u16x16 = _mm256_and_si256(raw_u16x16, abs_mask_u16x16);
3534
+ __m256i nan_detect_i16x16 = _mm256_cmpgt_epi16(abs_u16x16, nan_threshold_u16x16);
3535
+ __m256i lane_indices_u16x16 = _mm256_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
3536
+ __m256i valid_i16x16 = _mm256_cmpgt_epi16(_mm256_set1_epi16((short)remaining), lane_indices_u16x16);
3537
+ __m256i not_nan_valid_i16x16 = _mm256_andnot_si256(nan_detect_i16x16, valid_i16x16);
3538
+ __m256i less_i16x16 = _mm256_cmpgt_epi16(min_cmp_i16x16, data_cmp_i16x16);
3539
+ __m256i min_changed_i16x16 = _mm256_and_si256(less_i16x16, not_nan_valid_i16x16);
3540
+ min_cmp_i16x16 = _mm256_blendv_epi8(min_cmp_i16x16, data_cmp_i16x16, min_changed_i16x16);
3541
+ min_loop_cycle_u16x16 = _mm256_blendv_epi8(min_loop_cycle_u16x16, current_loop_cycle_u16x16,
3542
+ min_changed_i16x16);
3543
+ __m256i greater_i16x16 = _mm256_cmpgt_epi16(data_cmp_i16x16, max_cmp_i16x16);
3544
+ __m256i max_changed_i16x16 = _mm256_and_si256(greater_i16x16, not_nan_valid_i16x16);
3545
+ max_cmp_i16x16 = _mm256_blendv_epi8(max_cmp_i16x16, data_cmp_i16x16, max_changed_i16x16);
3546
+ max_loop_cycle_u16x16 = _mm256_blendv_epi8(max_loop_cycle_u16x16, current_loop_cycle_u16x16,
3547
+ max_changed_i16x16);
3548
+ }
3549
+
3550
+ nk_i16_t min_value_comparable = nk_reduce_min_i16x16_haswell_(min_cmp_i16x16);
3551
+ nk_i16_t max_value_comparable = nk_reduce_max_i16x16_haswell_(max_cmp_i16x16);
3552
+ if (min_value_comparable == 0x7FFF && max_value_comparable == (nk_i16_t)0x8000) {
3553
+ *min_value_ptr = NK_F16_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_F16_MIN,
3554
+ *max_index_ptr = NK_SIZE_MAX;
3555
+ return;
3556
+ }
3557
+ unsigned int min_lane, max_lane;
3558
+ {
3559
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(min_cmp_i16x16, _mm256_set1_epi16(min_value_comparable));
3560
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), min_loop_cycle_u16x16,
3561
+ value_match_b16x16);
3562
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
3563
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
3564
+ _mm256_set1_epi16((short)earliest_loop_cycle));
3565
+ min_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
3566
+ }
3567
+ {
3568
+ __m256i value_match_b16x16 = _mm256_cmpeq_epi16(max_cmp_i16x16, _mm256_set1_epi16(max_value_comparable));
3569
+ __m256i masked_cycle_u16x16 = _mm256_blendv_epi8(_mm256_set1_epi16((short)NK_U16_MAX), max_loop_cycle_u16x16,
3570
+ value_match_b16x16);
3571
+ nk_u16_t earliest_loop_cycle = nk_reduce_min_u16x16_haswell_(masked_cycle_u16x16);
3572
+ __m256i cycle_match_b16x16 = _mm256_cmpeq_epi16(masked_cycle_u16x16,
3573
+ _mm256_set1_epi16((short)earliest_loop_cycle));
3574
+ max_lane = _tzcnt_u32((unsigned int)_mm256_movemask_epi8(cycle_match_b16x16)) / 2;
3575
+ }
3576
+ nk_b256_vec_t loop_cycle_vec;
3577
+ loop_cycle_vec.ymm = min_loop_cycle_u16x16;
3578
+ *min_index_ptr = (nk_size_t)loop_cycle_vec.u16s[min_lane] * 16 + min_lane;
3579
+ loop_cycle_vec.ymm = max_loop_cycle_u16x16;
3580
+ *max_index_ptr = (nk_size_t)loop_cycle_vec.u16s[max_lane] * 16 + max_lane;
3581
+ nk_i16_t min_sign = min_value_comparable >> 15;
3582
+ *min_value_ptr = (nk_f16_t)((nk_u16_t)min_value_comparable ^ ((nk_u16_t)min_sign >> 1));
3583
+ nk_i16_t max_sign = max_value_comparable >> 15;
3584
+ *max_value_ptr = (nk_f16_t)((nk_u16_t)max_value_comparable ^ ((nk_u16_t)max_sign >> 1));
3585
+ }
3586
+
3587
+ NK_PUBLIC void nk_reduce_minmax_f16_haswell( //
3588
+ nk_f16_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3589
+ nk_f16_t *min_value_ptr, nk_size_t *min_index_ptr, //
3590
+ nk_f16_t *max_value_ptr, nk_size_t *max_index_ptr) {
3591
+
3592
+ nk_size_t stride_elements = stride_bytes / sizeof(nk_f16_t);
3593
+ int aligned = (stride_bytes % sizeof(nk_f16_t) == 0);
3594
+ if (count == 0)
3595
+ *min_value_ptr = NK_F16_MAX, *min_index_ptr = NK_SIZE_MAX, *max_value_ptr = NK_F16_MIN,
3596
+ *max_index_ptr = NK_SIZE_MAX;
3597
+ else if (!aligned)
3598
+ nk_reduce_minmax_f16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
3599
+ max_index_ptr);
3600
+ else if (stride_elements == 1 && count > (nk_size_t)(NK_U16_MAX + 1) * 16) {
3601
+ nk_size_t left_count = count / 2;
3602
+ nk_f16_t left_min, right_min, left_max, right_max;
3603
+ nk_size_t left_min_index, right_min_index, left_max_index, right_max_index;
3604
+ nk_reduce_minmax_f16_haswell(data_ptr, left_count, stride_bytes, &left_min, &left_min_index, &left_max,
3605
+ &left_max_index);
3606
+ nk_reduce_minmax_f16_haswell(data_ptr + left_count, count - left_count, stride_bytes, &right_min,
3607
+ &right_min_index, &right_max, &right_max_index);
3608
+ if (nk_f16_order_serial(right_min, left_min) < 0)
3609
+ *min_value_ptr = right_min, *min_index_ptr = left_count + right_min_index;
3610
+ else *min_value_ptr = left_min, *min_index_ptr = left_min_index;
3611
+ if (nk_f16_order_serial(right_max, left_max) > 0)
3612
+ *max_value_ptr = right_max, *max_index_ptr = left_count + right_max_index;
3613
+ else *max_value_ptr = left_max, *max_index_ptr = left_max_index;
3614
+ }
3615
+ else if (stride_elements == 1)
3616
+ nk_reduce_minmax_f16_haswell_contiguous_(data_ptr, count, min_value_ptr, min_index_ptr, max_value_ptr,
3617
+ max_index_ptr);
3618
+ else
3619
+ nk_reduce_minmax_f16_serial(data_ptr, count, stride_bytes, min_value_ptr, min_index_ptr, max_value_ptr,
3620
+ max_index_ptr);
3621
+ }
3622
+
3623
+ NK_INTERNAL void nk_reduce_moments_i4_haswell_contiguous_( //
3624
+ nk_i4x2_t const *data_ptr, nk_size_t count, //
3625
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
3626
+
3627
+ __m256i mask_0f_i8x32 = _mm256_set1_epi8(0x0F);
3628
+ __m256i eight_i8x32 = _mm256_set1_epi8(8);
3629
+ __m256i zero_i8x32 = _mm256_setzero_si256();
3630
+ __m256i sq_lut_u8x32 = _mm256_setr_epi8( //
3631
+ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, (char)144, (char)169, //
3632
+ (char)196, (char)225, //
3633
+ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, (char)144, (char)169, //
3634
+ (char)196, (char)225);
3635
+ __m256i sum_u64x4 = _mm256_setzero_si256();
3636
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
3637
+ nk_size_t count_bytes = nk_size_divide_round_up_(count, 2);
3638
+ unsigned char const *ptr = (unsigned char const *)data_ptr;
3639
+ while (count_bytes > 0) {
3640
+ nk_b256_vec_t raw_vec;
3641
+ if (count_bytes < 32) {
3642
+ nk_partial_load_b8x32_serial_(ptr, &raw_vec, count_bytes);
3643
+ count_bytes = 0;
3644
+ }
3645
+ else {
3646
+ raw_vec.ymm = _mm256_loadu_si256((__m256i const *)ptr);
3647
+ ptr += 32, count_bytes -= 32;
3648
+ }
3649
+ __m256i raw_i8x32 = raw_vec.ymm;
3650
+ __m256i low_u4_u8x32 = _mm256_and_si256(raw_i8x32, mask_0f_i8x32);
3651
+ __m256i high_u4_u8x32 = _mm256_and_si256(_mm256_srli_epi16(raw_i8x32, 4), mask_0f_i8x32);
3652
+ __m256i low_biased_u4_u8x32 = _mm256_xor_si256(low_u4_u8x32, eight_i8x32);
3653
+ __m256i high_biased_u4_u8x32 = _mm256_xor_si256(high_u4_u8x32, eight_i8x32);
3654
+ __m256i pair_sum_u8x32 = _mm256_add_epi8(low_biased_u4_u8x32, high_biased_u4_u8x32);
3655
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(pair_sum_u8x32, zero_i8x32));
3656
+ __m256i low_sq_u8x32 = _mm256_shuffle_epi8(sq_lut_u8x32, low_u4_u8x32);
3657
+ __m256i high_sq_u8x32 = _mm256_shuffle_epi8(sq_lut_u8x32, high_u4_u8x32);
3658
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_sad_epu8(low_sq_u8x32, zero_i8x32));
3659
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_sad_epu8(high_sq_u8x32, zero_i8x32));
3660
+ }
3661
+ // The XOR-8 bias adds 8 per nibble to the SAD total. Subtract 8 × total nibbles processed
3662
+ // (including zero-padded register bytes, where 0 XOR 8 = 8, signed = 0).
3663
+ nk_size_t nibbles_processed = nk_size_round_up_to_multiple_(nk_size_divide_round_up_(count, 2), 32) * 2;
3664
+ nk_i64_t sum = (nk_i64_t)(nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4) -
3665
+ (nk_i64_t)8 * (nk_i64_t)nibbles_processed;
3666
+ // sumsq uses sq_lut[0]=0 for zero-padded nibbles, so no register-padding correction needed.
3667
+ nk_u64_t sumsq = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_u64x4);
3668
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
3669
+ }
3670
+
3671
+ NK_PUBLIC void nk_reduce_moments_i4_haswell( //
3672
+ nk_i4x2_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3673
+ nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
3674
+
3675
+ count = nk_size_round_up_to_multiple_(count, 2);
3676
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
3677
+ else if (stride_bytes == 1) nk_reduce_moments_i4_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
3678
+ else nk_reduce_moments_i4_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3679
+ }
3680
+
3681
+ NK_INTERNAL void nk_reduce_moments_u4_haswell_contiguous_( //
3682
+ nk_u4x2_t const *data_ptr, nk_size_t count, //
3683
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
3684
+
3685
+ __m256i mask_0f_i8x32 = _mm256_set1_epi8(0x0F);
3686
+ __m256i zero_i8x32 = _mm256_setzero_si256();
3687
+ __m256i sq_lut_u8x32 = _mm256_setr_epi8( //
3688
+ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, (char)144, (char)169, //
3689
+ (char)196, (char)225, //
3690
+ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, (char)144, (char)169, //
3691
+ (char)196, (char)225);
3692
+ __m256i sum_u64x4 = _mm256_setzero_si256();
3693
+ __m256i sumsq_u64x4 = _mm256_setzero_si256();
3694
+ nk_size_t count_bytes = count / 2;
3695
+ unsigned char const *ptr = (unsigned char const *)data_ptr;
3696
+ while (count_bytes > 0) {
3697
+ nk_b256_vec_t raw_vec;
3698
+ if (count_bytes < 32) {
3699
+ nk_partial_load_b8x32_serial_(ptr, &raw_vec, count_bytes);
3700
+ count_bytes = 0;
3701
+ }
3702
+ else {
3703
+ raw_vec.ymm = _mm256_loadu_si256((__m256i const *)ptr);
3704
+ ptr += 32, count_bytes -= 32;
3705
+ }
3706
+ __m256i raw_i8x32 = raw_vec.ymm;
3707
+ __m256i low_u4_u8x32 = _mm256_and_si256(raw_i8x32, mask_0f_i8x32);
3708
+ __m256i high_u4_u8x32 = _mm256_and_si256(_mm256_srli_epi16(raw_i8x32, 4), mask_0f_i8x32);
3709
+ __m256i pair_sum_u8x32 = _mm256_add_epi8(low_u4_u8x32, high_u4_u8x32);
3710
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(pair_sum_u8x32, zero_i8x32));
3711
+ __m256i low_sq_u8x32 = _mm256_shuffle_epi8(sq_lut_u8x32, low_u4_u8x32);
3712
+ __m256i high_sq_u8x32 = _mm256_shuffle_epi8(sq_lut_u8x32, high_u4_u8x32);
3713
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_sad_epu8(low_sq_u8x32, zero_i8x32));
3714
+ sumsq_u64x4 = _mm256_add_epi64(sumsq_u64x4, _mm256_sad_epu8(high_sq_u8x32, zero_i8x32));
3715
+ }
3716
+ nk_u64_t sum = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4);
3717
+ nk_u64_t sumsq = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sumsq_u64x4);
3718
+ *sum_ptr = sum, *sumsq_ptr = sumsq;
3719
+ }
3720
+
3721
+ NK_PUBLIC void nk_reduce_moments_u4_haswell( //
3722
+ nk_u4x2_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3723
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
3724
+
3725
+ count = nk_size_round_up_to_multiple_(count, 2);
3726
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
3727
+ else if (stride_bytes == 1) nk_reduce_moments_u4_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
3728
+ else nk_reduce_moments_u4_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3729
+ }
3730
+
3731
+ NK_INTERNAL void nk_reduce_moments_u1_haswell_contiguous_( //
3732
+ nk_u1x8_t const *data_ptr, nk_size_t count, //
3733
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
3734
+
3735
+ __m256i lut_i8x32 = _mm256_setr_epi8( //
3736
+ 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
3737
+ __m256i mask_0f_i8x32 = _mm256_set1_epi8(0x0F);
3738
+ __m256i zero_i8x32 = _mm256_setzero_si256();
3739
+ __m256i sum_u64x4 = _mm256_setzero_si256();
3740
+ nk_size_t count_bytes = count / 8;
3741
+ unsigned char const *ptr = (unsigned char const *)data_ptr;
3742
+ while (count_bytes > 0) {
3743
+ nk_b256_vec_t raw_vec;
3744
+ if (count_bytes < 32) {
3745
+ nk_partial_load_b8x32_serial_(ptr, &raw_vec, count_bytes);
3746
+ count_bytes = 0;
3747
+ }
3748
+ else {
3749
+ raw_vec.ymm = _mm256_loadu_si256((__m256i const *)ptr);
3750
+ ptr += 32, count_bytes -= 32;
3751
+ }
3752
+ __m256i raw_i8x32 = raw_vec.ymm;
3753
+ __m256i low_nibble_u8x32 = _mm256_and_si256(raw_i8x32, mask_0f_i8x32);
3754
+ __m256i high_nibble_u8x32 = _mm256_and_si256(_mm256_srli_epi16(raw_i8x32, 4), mask_0f_i8x32);
3755
+ __m256i popcnt_u8x32 = _mm256_add_epi8(_mm256_shuffle_epi8(lut_i8x32, low_nibble_u8x32),
3756
+ _mm256_shuffle_epi8(lut_i8x32, high_nibble_u8x32));
3757
+ sum_u64x4 = _mm256_add_epi64(sum_u64x4, _mm256_sad_epu8(popcnt_u8x32, zero_i8x32));
3758
+ }
3759
+ nk_u64_t sum = (nk_u64_t)nk_reduce_add_i64x4_haswell_(sum_u64x4);
3760
+ *sum_ptr = sum, *sumsq_ptr = sum;
3761
+ }
3762
+
3763
+ NK_PUBLIC void nk_reduce_moments_u1_haswell( //
3764
+ nk_u1x8_t const *data_ptr, nk_size_t count, nk_size_t stride_bytes, //
3765
+ nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
3766
+
3767
+ count = nk_size_round_up_to_multiple_(count, 8);
3768
+ if (count == 0) *sum_ptr = 0, *sumsq_ptr = 0;
3769
+ else if (stride_bytes == 1) nk_reduce_moments_u1_haswell_contiguous_(data_ptr, count, sum_ptr, sumsq_ptr);
3770
+ else nk_reduce_moments_u1_serial(data_ptr, count, stride_bytes, sum_ptr, sumsq_ptr);
3771
+ }
3772
+
3773
+ #if defined(__clang__)
3774
+ #pragma clang attribute pop
3775
+ #elif defined(__GNUC__)
3776
+ #pragma GCC pop_options
3777
+ #endif
3778
+
3779
+ #if defined(__cplusplus)
3780
+ } // extern "C"
3781
+ #endif
3782
+
3783
+ #endif // NK_TARGET_HASWELL
3784
+ #endif // NK_TARGET_X8664_
3785
+ #endif // NK_REDUCE_HASWELL_H