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,2867 @@
1
+ /**
2
+ * @brief SWAR-accelerated Batched Dot Products for SIMD-free CPUs.
3
+ * @file include/numkong/dots/serial.h
4
+ * @author Ash Vardanian
5
+ * @date December 27, 2025
6
+ *
7
+ * @sa include/numkong/dots.h for API overview and use cases
8
+ *
9
+ * This file provides two macro families for generating GEMM kernels:
10
+ *
11
+ * - nk_define_dots_packed_: vectorized inner-products between rows of A and Bᵀ
12
+ * - nk_define_dots_symmetric_: vectorized inner-products between rows and columns of A
13
+ *
14
+ * Both use the same B packing format (see below), enabling pack-once-use-anywhere.
15
+ *
16
+ * @section packing B Matrix Packing Format
17
+ *
18
+ * Computing C = A × Bᵀ where:
19
+ *
20
+ * - A[row_count, depth] row-major: A[i, k] at address A + i × lda + k
21
+ * - B[column_count, depth] row-major (pre-transposed): B[j, k] at address B + j × ldb + k
22
+ * - C[row_count, column_count] row-major: C[i, j] at address C + i × ldc + j
23
+ *
24
+ * The API convention stores B as Bᵀ for efficient SIMD access:
25
+ *
26
+ * - A[i, k:k+4] is contiguous in row-major (good)
27
+ * - B[j, k:k+4] is contiguous in row-major (good - already transposed)
28
+ *
29
+ * Packing adds row grouping (group_size = 16) for:
30
+ *
31
+ * - Zero-padding on edges (avoids boundary checks in inner loop)
32
+ * - Cache-friendly blocking in outer loops
33
+ *
34
+ * Memory layout example - B[8, 8] with 8 output columns (j), 8 depth (k):
35
+ *
36
+ * k=0 k=1 k=2 k=3 k=4 k=5 k=6 k=7
37
+ * ┌─────────────────────────────────────────────────┐
38
+ * j=0 │ a0 a1 a2 a3 a4 a5 a6 a7 │
39
+ * j=1 │ b0 b1 b2 b3 b4 b5 b6 b7 │
40
+ * j=2 │ c0 c1 c2 c3 c4 c5 c6 c7 │
41
+ * j=3 │ d0 d1 d2 d3 d4 d5 d6 d7 │
42
+ * j=4 │ e0 e1 e2 e3 e4 e5 e6 e7 │
43
+ * j=5 │ f0 f1 f2 f3 f4 f5 f6 f7 │
44
+ * j=6 │ g0 g1 g2 g3 g4 g5 g6 g7 │
45
+ * j=7 │ h0 h1 h2 h3 h4 h5 h6 h7 │
46
+ * └─────────────────────────────────────────────────┘
47
+ *
48
+ * Packed as B_packed[column_count_padded, depth] (grouped for alignment):
49
+ *
50
+ * Group 0 (j=0..7, padded to 16):
51
+ * ┌───────────────────────────────────┐
52
+ * │ a0 a1 a2 a3 a4 a5 a6 a7 │ j=0 │ ← row 0 copied as-is
53
+ * │ b0 b1 b2 b3 b4 b5 b6 b7 │ j=1 │
54
+ * │ c0 c1 c2 c3 c4 c5 c6 c7 │ j=2 │
55
+ * │ d0 d1 d2 d3 d4 d5 d6 d7 │ j=3 │
56
+ * │ e0 e1 e2 e3 e4 e5 e6 e7 │ j=4 │
57
+ * │ f0 f1 f2 f3 f4 f5 f6 f7 │ j=5 │
58
+ * │ g0 g1 g2 g3 g4 g5 g6 g7 │ j=6 │
59
+ * │ h0 h1 h2 h3 h4 h5 h6 h7 │ j=7 │
60
+ * │ 00 00 00 00 00 00 00 00 │ padding │
61
+ * │ ... │ ... │
62
+ * └───────────────────────────────────┘
63
+ *
64
+ * Addressing formula for B_packed[j, k]:
65
+ *
66
+ * group = j / group_size
67
+ * j_in_group = j % group_size
68
+ * B_packed[j, k] = packed[group * group_size * depth + j_in_group * depth + k]
69
+ *
70
+ * Inner loop accesses B_packed[j, k:k+simd] which is contiguous - just ptr + k.
71
+ */
72
+
73
+ #ifndef NK_DOTS_SERIAL_H
74
+ #define NK_DOTS_SERIAL_H
75
+
76
+ #include "numkong/types.h"
77
+ #include "numkong/cast/serial.h" // `nk_partial_load_b32x4_serial_`
78
+ #include "numkong/dot/serial.h" // `nk_dot_f32x4_state_serial_t`
79
+ #include "numkong/spatial/serial.h" // `nk_f32_sqrt_serial`
80
+ #include "numkong/reduce.h" // `nk_reduce_moments_*`
81
+
82
+ /* GCC's -Wstringop-overflow produces false positives on the padded accumulator arrays
83
+ * in nk_define_cross_symmetric_ macro expansions (accumulators[4][7] with runtime indexing). */
84
+ #if defined(__GNUC__) && !defined(__clang__)
85
+ #pragma GCC diagnostic push
86
+ #pragma GCC diagnostic ignored "-Wstringop-overflow"
87
+ #endif
88
+
89
+ #if defined(__cplusplus)
90
+ extern "C" {
91
+ #endif
92
+
93
+ /* Packed buffer header (64-byte aligned).
94
+ * Used by all packed matmul backends (serial, NEON, AVX-512, SVE).
95
+ *
96
+ * Important units clarification:
97
+ * - For types where dimensions_per_value = 1 (f32, i8, u8, etc.): dimensions == values
98
+ * - For sub-byte types (i4x2, u4x2): dimensions ≠ values
99
+ * - dimensions = individual 4-bit nibbles (e.g., 128 nibbles)
100
+ * - values = storage bytes containing nibbles (e.g., 64 bytes for 128 nibbles)
101
+ * - dimensions_per_value = 2 (2 nibbles per byte)
102
+ */
103
+ typedef struct {
104
+ nk_u32_t column_count; // Actual number of columns (not padded)
105
+ nk_u32_t depth_dimensions; // Logical depth in dimensions (nibbles for i4/u4, values for i8/f32)
106
+ nk_u32_t depth_padded_values; // Padded depth in storage values (bytes for i4/u4, values for i8/f32)
107
+ nk_u32_t reserved[13]; // Padding to 64 bytes
108
+ } nk_cross_packed_buffer_header_t;
109
+
110
+ /* Norm compute helpers for packing.
111
+ * Each computes the norm (sum-of-squares or popcount) of a contiguous row.
112
+ * Used by `nk_define_cross_pack_` to append per-column norms to packed buffers.
113
+ */
114
+ NK_INTERNAL nk_f64_t nk_dots_reduce_sumsq_f64_(nk_f64_t const *data, nk_size_t count) {
115
+ nk_f64_t sum, sumsq;
116
+ nk_reduce_moments_f64(data, count, sizeof(nk_f64_t), &sum, &sumsq);
117
+ return sumsq;
118
+ }
119
+ NK_INTERNAL nk_f64_t nk_dots_reduce_sumsq_f32_(nk_f32_t const *data, nk_size_t count) {
120
+ nk_f64_t sum, sumsq;
121
+ nk_reduce_moments_f32(data, count, sizeof(nk_f32_t), &sum, &sumsq);
122
+ return sumsq;
123
+ }
124
+ NK_INTERNAL nk_f32_t nk_dots_reduce_sumsq_f16_(nk_f16_t const *data, nk_size_t count) {
125
+ nk_f32_t sum, sumsq;
126
+ nk_reduce_moments_f16(data, count, sizeof(nk_f16_t), &sum, &sumsq);
127
+ return sumsq;
128
+ }
129
+ NK_INTERNAL nk_f32_t nk_dots_reduce_sumsq_bf16_(nk_bf16_t const *data, nk_size_t count) {
130
+ nk_f32_t sum, sumsq;
131
+ nk_reduce_moments_bf16(data, count, sizeof(nk_bf16_t), &sum, &sumsq);
132
+ return sumsq;
133
+ }
134
+ NK_INTERNAL nk_f32_t nk_dots_reduce_sumsq_e4m3_(nk_e4m3_t const *data, nk_size_t count) {
135
+ nk_f32_t sum, sumsq;
136
+ nk_reduce_moments_e4m3(data, count, sizeof(nk_e4m3_t), &sum, &sumsq);
137
+ return sumsq;
138
+ }
139
+ NK_INTERNAL nk_f32_t nk_dots_reduce_sumsq_e5m2_(nk_e5m2_t const *data, nk_size_t count) {
140
+ nk_f32_t sum, sumsq;
141
+ nk_reduce_moments_e5m2(data, count, sizeof(nk_e5m2_t), &sum, &sumsq);
142
+ return sumsq;
143
+ }
144
+ NK_INTERNAL nk_f32_t nk_dots_reduce_sumsq_e2m3_(nk_e2m3_t const *data, nk_size_t count) {
145
+ nk_f32_t sum, sumsq;
146
+ nk_reduce_moments_e2m3(data, count, sizeof(nk_e2m3_t), &sum, &sumsq);
147
+ return sumsq;
148
+ }
149
+ NK_INTERNAL nk_f32_t nk_dots_reduce_sumsq_e3m2_(nk_e3m2_t const *data, nk_size_t count) {
150
+ nk_f32_t sum, sumsq;
151
+ nk_reduce_moments_e3m2(data, count, sizeof(nk_e3m2_t), &sum, &sumsq);
152
+ return sumsq;
153
+ }
154
+ NK_INTERNAL nk_u32_t nk_dots_reduce_sumsq_i8_(nk_i8_t const *data, nk_size_t count) {
155
+ nk_i64_t sum;
156
+ nk_u64_t sumsq;
157
+ nk_reduce_moments_i8(data, count, sizeof(nk_i8_t), &sum, &sumsq);
158
+ return (nk_u32_t)sumsq;
159
+ }
160
+ NK_INTERNAL nk_u32_t nk_dots_reduce_sumsq_u8_(nk_u8_t const *data, nk_size_t count) {
161
+ nk_u64_t sum, sumsq;
162
+ nk_reduce_moments_u8(data, count, sizeof(nk_u8_t), &sum, &sumsq);
163
+ return (nk_u32_t)sumsq;
164
+ }
165
+ NK_INTERNAL nk_u32_t nk_dots_reduce_sumsq_i4_(nk_i4x2_t const *data, nk_size_t count) {
166
+ nk_i64_t sum;
167
+ nk_u64_t sumsq;
168
+ nk_reduce_moments_i4(data, count, sizeof(nk_i4x2_t), &sum, &sumsq);
169
+ return (nk_u32_t)sumsq;
170
+ }
171
+ NK_INTERNAL nk_u32_t nk_dots_reduce_sumsq_u4_(nk_u4x2_t const *data, nk_size_t count) {
172
+ nk_u64_t sum, sumsq;
173
+ nk_reduce_moments_u4(data, count, sizeof(nk_u4x2_t), &sum, &sumsq);
174
+ return (nk_u32_t)sumsq;
175
+ }
176
+ NK_INTERNAL nk_u32_t nk_dots_reduce_sum_u1_(nk_u1x8_t const *data, nk_size_t count_bits) {
177
+ nk_u64_t sum, sumsq;
178
+ nk_reduce_moments_u1(data, count_bits, sizeof(nk_u1x8_t), &sum, &sumsq);
179
+ return (nk_u32_t)sum;
180
+ }
181
+
182
+ /* Combined moment trampolines for compensated GEMM.
183
+ * Each computes BOTH sum and norm (sum-of-squares) in a single nk_reduce_moments call.
184
+ * Used by nk_define_cross_compensated_pack_ to store both in the packed buffer.
185
+ */
186
+ NK_INTERNAL void nk_dots_reduce_moments_i8_(nk_i8_t const *data, nk_size_t count, nk_i32_t *sum, nk_u32_t *norm) {
187
+ nk_i64_t s;
188
+ nk_u64_t sq;
189
+ nk_reduce_moments_i8(data, count, sizeof(nk_i8_t), &s, &sq);
190
+ *sum = (nk_i32_t)s;
191
+ *norm = (nk_u32_t)sq;
192
+ }
193
+ NK_INTERNAL void nk_dots_reduce_moments_u8_(nk_u8_t const *data, nk_size_t count, nk_u32_t *sum, nk_u32_t *norm) {
194
+ nk_u64_t s, sq;
195
+ nk_reduce_moments_u8(data, count, sizeof(nk_u8_t), &s, &sq);
196
+ *sum = (nk_u32_t)s;
197
+ *norm = (nk_u32_t)sq;
198
+ }
199
+ NK_INTERNAL void nk_dots_reduce_moments_i4_(nk_i4x2_t const *data, nk_size_t count, nk_i32_t *sum, nk_u32_t *norm) {
200
+ nk_i64_t s;
201
+ nk_u64_t sq;
202
+ nk_reduce_moments_i4(data, count, sizeof(nk_i4x2_t), &s, &sq);
203
+ *sum = (nk_i32_t)s;
204
+ *norm = (nk_u32_t)sq;
205
+ }
206
+
207
+ /* A-row sum helpers for compensated GEMM finalization.
208
+ * i8/u8: no A-side correction needed, stubs return 0.
209
+ * i4: needs A-side sum for correction term.
210
+ */
211
+ NK_INTERNAL nk_i32_t nk_dots_reduce_sum_i8_stub_(nk_i8_t const *d, nk_size_t c) {
212
+ nk_unused_(d);
213
+ nk_unused_(c);
214
+ return 0;
215
+ }
216
+ NK_INTERNAL nk_i32_t nk_dots_reduce_sum_u8_stub_(nk_u8_t const *d, nk_size_t c) {
217
+ nk_unused_(d);
218
+ nk_unused_(c);
219
+ return 0;
220
+ }
221
+ NK_INTERNAL nk_i32_t nk_dots_reduce_sum_i4_(nk_i4x2_t const *data, nk_size_t count) {
222
+ nk_i64_t sum;
223
+ nk_u64_t sumsq;
224
+ nk_reduce_moments_i4(data, count, sizeof(nk_i4x2_t), &sum, &sumsq);
225
+ return (nk_i32_t)sum;
226
+ }
227
+
228
+ /**
229
+ * @brief Generates function to calculate packed B matrix buffer size for GEMM micro-kernels.
230
+ *
231
+ * Memory layout: B_packed[column_count, depth_padded] with header storing metadata.
232
+ * Buffer size: sizeof(header) + column_count × depth_padded × sizeof(intermediate_type) + column_count × sizeof(norm)
233
+ * Depth padding logic: Round up to `depth_simd_dimensions` multiple, then add `depth_simd_dimensions`
234
+ * if stride is power-of-2.
235
+ *
236
+ * @param api_name Operation name (hammings, dots)
237
+ * @param input_type_name Original type's name of B matrix values (i4, f16, bf16, e4m3, e5m2, f32, etc.)
238
+ * @param isa_suffix Platform Instruct Set Architecture suffix (serial, haswell, icelake, etc.)
239
+ * @param input_type Original type of B matrix values (i4x2, f16, bf16, e4m3, e5m2, f32, etc.)
240
+ * @param intermediate_type Internal storage type in packed buffer (often bf16 or f32 for mixed precision)
241
+ * @param norm_value_type Type of per-column norm values (f32, f64, u32) appended after packed data
242
+ * @param depth_simd_dimensions SIMD vector width in values for this platform/type combination
243
+ * @param dimensions_per_value Number of logical dimensions in a single value of input_type_name.
244
+ */
245
+ #define nk_define_cross_pack_size_(api_name, input_type_name, isa_suffix, input_value_type, packed_value_type, \
246
+ norm_value_type, depth_simd_dimensions, dimensions_per_value) \
247
+ NK_PUBLIC nk_size_t nk_##api_name##_packed_size_##input_type_name##_##isa_suffix(nk_size_t column_count, \
248
+ nk_size_t depth) { \
249
+ /* depth is always in logical dimensions (nibbles for i4, bytes for i8, etc.) */ \
250
+ /* depth_simd_dimensions is also in logical dimensions */ \
251
+ \
252
+ /* Step 1: Pad depth in dimensions */ \
253
+ nk_size_t depth_dimensions_padded = nk_size_round_up_to_multiple_(depth, depth_simd_dimensions); \
254
+ \
255
+ /* Step 2: Convert dimensions to storage values */ \
256
+ nk_size_t depth_values_padded = nk_size_divide_round_up_(depth_dimensions_padded, dimensions_per_value); \
257
+ \
258
+ /* Step 3: Calculate stride in bytes for power-of-2 check */ \
259
+ nk_size_t const stride_bytes = depth_values_padded * sizeof(nk_##packed_value_type##_t); \
260
+ \
261
+ /* Step 4: Break power-of-2 strides for cache associativity */ \
262
+ if ((stride_bytes & (stride_bytes - 1)) == 0 && stride_bytes > 0) { \
263
+ /* Add one SIMD step worth of storage values */ \
264
+ depth_values_padded += nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
265
+ } \
266
+ \
267
+ /* Step 5: Return total buffer size (packed data + per-column norms) */ \
268
+ return sizeof(nk_cross_packed_buffer_header_t) + \
269
+ column_count * depth_values_padded * sizeof(nk_##packed_value_type##_t) + \
270
+ column_count * sizeof(nk_##norm_value_type##_t); \
271
+ }
272
+
273
+ /**
274
+ * @brief Generates pack function using SIMD load/store helpers.
275
+ *
276
+ * Packs the B matrix into padded row-major layout with optional type conversion,
277
+ * using vectorized load/store for the bulk copy and a small scalar tail for padding.
278
+ *
279
+ * @param vec_type SIMD vector type (nk_b512_vec_t, nk_b256_vec_t, nk_b128_vec_t)
280
+ * @param load_fn Full load: void fn(void const*, vec_type*)
281
+ * @param partial_load_fn Masked/partial load: void fn(void const*, vec_type*, nk_size_t)
282
+ * @param store_fn Full store: void fn(vec_type const*, void*)
283
+ * @param partial_store_fn Masked/partial store: void fn(vec_type const*, void*, nk_size_t)
284
+ * @param simd_width Elements per SIMD load/store operation
285
+ */
286
+ #define nk_define_cross_pack_(api_name, input_type_name, isa_suffix, input_value_type, packed_value_type, vec_type, \
287
+ load_fn, partial_load_fn, store_fn, partial_store_fn, simd_width, norm_value_type, \
288
+ compute_norm_fn, depth_simd_dimensions, dimensions_per_value) \
289
+ NK_PUBLIC void nk_##api_name##_pack_##input_type_name##_##isa_suffix( \
290
+ nk_##input_value_type##_t const *b, nk_size_t column_count, nk_size_t depth, nk_size_t b_stride_in_bytes, \
291
+ void *b_packed) { \
292
+ nk_size_t depth_dimensions_padded = nk_size_round_up_to_multiple_(depth, depth_simd_dimensions); \
293
+ nk_size_t depth_values_padded = nk_size_divide_round_up_(depth_dimensions_padded, dimensions_per_value); \
294
+ nk_size_t const stride_bytes = depth_values_padded * sizeof(nk_##packed_value_type##_t); \
295
+ if ((stride_bytes & (stride_bytes - 1)) == 0 && stride_bytes > 0) \
296
+ depth_values_padded += nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
297
+ nk_size_t const depth_in_values = nk_size_divide_round_up_(depth, dimensions_per_value); \
298
+ \
299
+ nk_cross_packed_buffer_header_t *header = (nk_cross_packed_buffer_header_t *)b_packed; \
300
+ header->column_count = (nk_u32_t)column_count; \
301
+ header->depth_dimensions = (nk_u32_t)depth; \
302
+ header->depth_padded_values = (nk_u32_t)depth_values_padded; \
303
+ \
304
+ nk_##packed_value_type##_t *packed = (nk_##packed_value_type##_t *)((char *)b_packed + \
305
+ sizeof(nk_cross_packed_buffer_header_t)); \
306
+ nk_size_t const full_chunks = depth_in_values / (simd_width); \
307
+ nk_size_t const remainder = depth_in_values % (simd_width); \
308
+ \
309
+ for (nk_size_t column_index = 0; column_index < column_count; ++column_index) { \
310
+ nk_##input_value_type##_t const *source_row = \
311
+ (nk_##input_value_type##_t const *)((char const *)b + column_index * b_stride_in_bytes); \
312
+ nk_##packed_value_type##_t *destination_row = packed + column_index * depth_values_padded; \
313
+ for (nk_size_t chunk = 0; chunk < full_chunks; ++chunk) { \
314
+ vec_type vec; \
315
+ load_fn(source_row + chunk * (simd_width), &vec); \
316
+ store_fn(&vec, destination_row + chunk * (simd_width)); \
317
+ } \
318
+ if (remainder > 0) { \
319
+ vec_type vec; \
320
+ partial_load_fn(source_row + full_chunks * (simd_width), &vec, remainder); \
321
+ partial_store_fn(&vec, destination_row + full_chunks * (simd_width), remainder); \
322
+ } \
323
+ for (nk_size_t pad = depth_in_values; pad < depth_values_padded; ++pad) destination_row[pad] = 0; \
324
+ } \
325
+ \
326
+ nk_size_t const total_values = column_count * depth_values_padded; \
327
+ nk_##norm_value_type##_t *norms = (nk_##norm_value_type##_t *)(packed + total_values); \
328
+ for (nk_size_t column_index = 0; column_index < column_count; ++column_index) { \
329
+ nk_##input_value_type##_t const *source_row = \
330
+ (nk_##input_value_type##_t const *)((char const *)b + column_index * b_stride_in_bytes); \
331
+ norms[column_index] = compute_norm_fn(source_row, depth); \
332
+ } \
333
+ }
334
+
335
+ /**
336
+ * @brief Generates function to calculate packed B matrix buffer size for compensated GEMM.
337
+ *
338
+ * Like nk_define_cross_pack_size_ but the buffer stores BOTH norms AND column sums.
339
+ * Layout: [ Header 64B ] [ Packed data ] [ Norms (norm_type) ] [ Column sums (sum_type) ]
340
+ * Norms first → existing nk_define_cross_normalized_packed_ reads norms at the same offset.
341
+ */
342
+ #define nk_define_cross_compensated_pack_size_(api_name, input_type_name, isa_suffix, input_value_type, \
343
+ packed_value_type, sum_value_type, norm_value_type, \
344
+ depth_simd_dimensions, dimensions_per_value) \
345
+ NK_PUBLIC nk_size_t nk_##api_name##_packed_size_##input_type_name##_##isa_suffix(nk_size_t column_count, \
346
+ nk_size_t depth) { \
347
+ nk_size_t depth_dimensions_padded = nk_size_round_up_to_multiple_(depth, depth_simd_dimensions); \
348
+ nk_size_t depth_values_padded = nk_size_divide_round_up_(depth_dimensions_padded, dimensions_per_value); \
349
+ nk_size_t const stride_bytes = depth_values_padded * sizeof(nk_##packed_value_type##_t); \
350
+ if ((stride_bytes & (stride_bytes - 1)) == 0 && stride_bytes > 0) { \
351
+ depth_values_padded += nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
352
+ } \
353
+ return sizeof(nk_cross_packed_buffer_header_t) + \
354
+ column_count * depth_values_padded * sizeof(nk_##packed_value_type##_t) + \
355
+ column_count * sizeof(nk_##norm_value_type##_t) + column_count * sizeof(nk_##sum_value_type##_t); \
356
+ }
357
+
358
+ /**
359
+ * @brief Like nk_define_cross_pack_ but stores both per-column norms AND column sums.
360
+ * Layout: [ Header 64B ] [ Packed data ] [ Norms (norm_type) ] [ Column sums (sum_type) ]
361
+ */
362
+ #define nk_define_cross_compensated_pack_(api_name, input_type_name, isa_suffix, input_value_type, packed_value_type, \
363
+ vec_type, load_fn, partial_load_fn, store_fn, partial_store_fn, simd_width, \
364
+ sum_value_type, norm_value_type, compute_moments_fn, depth_simd_dimensions, \
365
+ dimensions_per_value) \
366
+ NK_PUBLIC void nk_##api_name##_pack_##input_type_name##_##isa_suffix( \
367
+ nk_##input_value_type##_t const *b, nk_size_t column_count, nk_size_t depth, nk_size_t b_stride_in_bytes, \
368
+ void *b_packed) { \
369
+ nk_size_t depth_dimensions_padded = nk_size_round_up_to_multiple_(depth, depth_simd_dimensions); \
370
+ nk_size_t depth_values_padded = nk_size_divide_round_up_(depth_dimensions_padded, dimensions_per_value); \
371
+ nk_size_t const stride_bytes = depth_values_padded * sizeof(nk_##packed_value_type##_t); \
372
+ if ((stride_bytes & (stride_bytes - 1)) == 0 && stride_bytes > 0) \
373
+ depth_values_padded += nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
374
+ nk_size_t const depth_in_values = nk_size_divide_round_up_(depth, dimensions_per_value); \
375
+ \
376
+ nk_cross_packed_buffer_header_t *header = (nk_cross_packed_buffer_header_t *)b_packed; \
377
+ header->column_count = (nk_u32_t)column_count; \
378
+ header->depth_dimensions = (nk_u32_t)depth; \
379
+ header->depth_padded_values = (nk_u32_t)depth_values_padded; \
380
+ \
381
+ nk_##packed_value_type##_t *packed = (nk_##packed_value_type##_t *)((char *)b_packed + \
382
+ sizeof(nk_cross_packed_buffer_header_t)); \
383
+ nk_size_t const full_chunks = depth_in_values / (simd_width); \
384
+ nk_size_t const remainder = depth_in_values % (simd_width); \
385
+ \
386
+ for (nk_size_t column_index = 0; column_index < column_count; ++column_index) { \
387
+ nk_##input_value_type##_t const *source_row = \
388
+ (nk_##input_value_type##_t const *)((char const *)b + column_index * b_stride_in_bytes); \
389
+ nk_##packed_value_type##_t *destination_row = packed + column_index * depth_values_padded; \
390
+ for (nk_size_t chunk = 0; chunk < full_chunks; ++chunk) { \
391
+ vec_type vec; \
392
+ load_fn(source_row + chunk * (simd_width), &vec); \
393
+ store_fn(&vec, destination_row + chunk * (simd_width)); \
394
+ } \
395
+ if (remainder > 0) { \
396
+ vec_type vec; \
397
+ partial_load_fn(source_row + full_chunks * (simd_width), &vec, remainder); \
398
+ partial_store_fn(&vec, destination_row + full_chunks * (simd_width), remainder); \
399
+ } \
400
+ for (nk_size_t pad = depth_in_values; pad < depth_values_padded; ++pad) destination_row[pad] = 0; \
401
+ } \
402
+ \
403
+ nk_size_t const total_values = column_count * depth_values_padded; \
404
+ nk_##norm_value_type##_t *norms = (nk_##norm_value_type##_t *)(packed + total_values); \
405
+ nk_##sum_value_type##_t *col_sums = (nk_##sum_value_type##_t *)(norms + column_count); \
406
+ for (nk_size_t column_index = 0; column_index < column_count; ++column_index) { \
407
+ nk_##input_value_type##_t const *source_row = \
408
+ (nk_##input_value_type##_t const *)((char const *)b + column_index * b_stride_in_bytes); \
409
+ compute_moments_fn(source_row, depth, &col_sums[column_index], &norms[column_index]); \
410
+ } \
411
+ }
412
+
413
+ /**
414
+ * @brief Generates optimized GEMM implementation: C = A × Bᵀ with pre-packed B matrix.
415
+ *
416
+ * This macro creates a complete batched matrix multiplication kernel with THREE specialized
417
+ * code paths that are automatically selected based on the remaining work at each blocking level.
418
+ * The kernel requires B to be pre-packed using nk_define_cross_pack_ before invocation.
419
+ *
420
+ * @par Mathematical Operation
421
+ * C[row_count, column_count] = A[row_count, depth] × Bᵀ[column_count, depth]
422
+ * where operation can be dot product, Hamming distance, Jaccard similarity, etc.
423
+ *
424
+ * @par Three Kernel Variants for Adaptive Performance
425
+ *
426
+ * 1. @b 4×4 @b register @b tile @b kernel (primary path, ~80% of work):
427
+ * - Processes 4 rows of A × 4 columns of B simultaneously
428
+ * - Maintains 16 independent accumulators in registers (state_type[4][4])
429
+ * - Achieves maximum instruction-level parallelism (16 FMAs per depth iteration)
430
+ * - Used when: row_count ≥ 4 AND column_count ≥ 4
431
+ * - Performance: Peak throughput, optimal register utilization
432
+ *
433
+ * 2. @b 1×8 @b register @b tile @b kernel (edge case, ~15% of work):
434
+ * - Processes 1 row of A × 8 columns of B when remaining rows < 4
435
+ * - Maintains 8 independent accumulators (state_type[1][8])
436
+ * - Balances vectorization with low row count
437
+ * - Used when: row_count < 4 AND column_count ≥ 8
438
+ * - Performance: Better throughput than generic fallback for wide matrices
439
+ *
440
+ * 3. @b Generic @b fallback @b kernel (edge cases, ~5% of work):
441
+ * - Handles all irregular cases (row_count < 4 AND column_count < 8)
442
+ * - Single accumulator, minimal unrolling
443
+ * - Used for: Small tiles, remainder handling
444
+ * - Performance: Lower throughput but handles all edge cases correctly
445
+ *
446
+ * @par Cache Blocking Strategy (No Depth Blocking)
447
+ *
448
+ * Unlike traditional GEMM which blocks all three dimensions (M, N, K), this implementation
449
+ * deliberately omits depth (K) blocking for several reasons:
450
+ *
451
+ * 1. @b Streaming @b access @b pattern: A and B are read sequentially along depth dimension
452
+ * - Prefetcher-friendly access (hardware prefetch works well)
453
+ * - No cache reuse along depth within a single C[i,j] computation
454
+ *
455
+ * 2. @b Depth @b is @b typically @b small: For ML inference, depth is often 128-4096 values
456
+ * - Fits in L2/L3 cache for single row of A
457
+ * - B is pre-packed for optimal spatial locality
458
+ *
459
+ * 3. @b Simplicity @b and @b instruction @b cache @b efficiency:
460
+ * - Fewer nested loops = better instruction cache utilization
461
+ * - Simpler control flow = easier for compiler to optimize
462
+ *
463
+ * @par Pre-Packing Benefits
464
+ *
465
+ * B matrix is pre-packed using nk_define_cross_pack_ before kernel invocation:
466
+ * - @b Type @b conversion @b amortization: Convert B values once (e.g., bf16→f32) rather than
467
+ * per A row access. Saves (row_count - 1) × column_count conversions.
468
+ * - @b Cache @b line @b optimization: Pad depth to break power-of-2 strides that cause cache
469
+ * associativity conflicts (e.g., 8192 → 8200 values).
470
+ * - @b Spatial @b locality: Transpose B so columns are contiguous, enabling efficient SIMD loads.
471
+ *
472
+ * @par Loop Structure
473
+ *
474
+ * for column_block in columns (step: varies based on available columns):
475
+ * for row_block in rows (step: varies based on available rows):
476
+ * for row_tile in row_block (step: 4 or 1 depending on variant):
477
+ * for column_tile in column_block (step: 4 or 8 depending on variant):
478
+ * accumulator_tiles[row_tile][column_tile] = init_accumulator_fn()
479
+ * for depth_index in depth (step: depth_simd_dimensions):
480
+ * a_vectors = load_a_vec_fn(A[row_tile, depth_index])
481
+ * b_vectors = load_b_vec_fn(B_packed[column_tile, depth_index])
482
+ * accumulator_tiles = inner_product_fn(accumulator_tiles, a_vectors, b_vectors)
483
+ * results = reduce_accumulators_fn(accumulator_tiles)
484
+ * partial_store_fn(results, C[row_tile, column_tile])
485
+ *
486
+ * @par Generated Function
487
+ *
488
+ * nk_##api_name##_packed_##input_type_name##_##isa_suffix##_aligned_(
489
+ * A_matrix, B_packed_buffer, C_matrix, row_count, column_count, depth,
490
+ * A_stride_bytes, C_stride_bytes)
491
+ *
492
+ * @param api_name Operation family (dots, hammings, jaccards) for codegen namespace
493
+ * @param input_type_name Type identifier for codegen (f32, bf16, i8, u1, etc.)
494
+ * @param isa_suffix ISA backend identifier (serial, haswell, neon, sve, icelake, etc.)
495
+ * @param input_type C type of input matrix values (f32, bf16, i8, u1x8, etc.)
496
+ * @param intermediate_type Storage type in packed B buffer (often bf16 or f32 for mixed precision)
497
+ * @param output_type C type of output matrix C values (f32, u32, f64, etc.)
498
+ * @param vec_type SIMD vector type for depth dimension (e.g., __m256, nk_f32x8_t)
499
+ * @param state_type Accumulator state type (often vec_type or wider, e.g., __m256 or __m512)
500
+ * @param result_vec_type SIMD vector type for reduction results (e.g., __m128 for 4 f32 results)
501
+ * @param init_accumulator_fn Initialize accumulator: void fn(state_type*)
502
+ * @param load_a_vec_fn Full A vector load: vec_type fn(input_type const*, nk_size_t offset)
503
+ * @param partial_load_a_vec_fn Partial A load for remainder
504
+ * @param load_b_vec_fn Full B vector load: vec_type fn(intermediate_type const*, nk_size_t offset)
505
+ * @param partial_load_b_vec_fn Partial B load for remainder
506
+ * @param inner_product_fn Inner product accumulate
507
+ * @param reduce_accumulators_fn Reduce 4 accumulators
508
+ * @param store_fn Full-width store for results
509
+ * @param store_fn Full-width store for results
510
+ * @param partial_store_fn Partial store for results
511
+ * @param depth_simd_dimensions SIMD vector width in logical dimensions (e.g., 8 for f32 on AVX2, 128 for u1 on serial)
512
+ * @param dimensions_per_value Packing ratio: dimensions per storage value (1 for f32, 2 for i4x2, 8 for u1x8)
513
+ *
514
+ * @sa nk_define_cross_symmetric_ for symmetric C = A × Aᵀ computation (upper triangle only)
515
+ * @sa nk_define_cross_pack_size_ for calculating B_packed buffer size
516
+ * @sa nk_define_cross_pack_ for packing B matrix into optimized layout
517
+ * @sa include/numkong/set/serial.h for state type definitions
518
+ * @sa include/numkong/cast/serial.h for load/store function implementations
519
+ */
520
+ #define nk_define_cross_packed_(api_name, input_type_name, isa_suffix, input_value_type, packed_value_type, \
521
+ result_value_type, vec_type, state_type, result_vec_type, init_accumulator_fn, \
522
+ load_a_vec_fn, partial_load_a_vec_fn, load_b_vec_fn, partial_load_b_vec_fn, \
523
+ inner_product_fn, reduce_accumulators_fn, store_fn, partial_store_fn, \
524
+ depth_simd_dimensions, dimensions_per_value) \
525
+ NK_PUBLIC void nk_##api_name##_packed_##input_type_name##_##isa_suffix##_aligned_( \
526
+ nk_##input_value_type##_t const *a_matrix, void const *b_packed_buffer, nk_##result_value_type##_t *c_matrix, \
527
+ nk_size_t row_count, nk_size_t column_count, nk_size_t depth, nk_size_t a_stride_in_bytes, \
528
+ nk_size_t c_stride_in_bytes) { \
529
+ /* Read padded depth from header for correct stride calculation */ \
530
+ nk_cross_packed_buffer_header_t const *header = (nk_cross_packed_buffer_header_t const *)b_packed_buffer; \
531
+ nk_size_t const depth_padded = header->depth_padded_values; \
532
+ \
533
+ nk_##packed_value_type##_t const *packed_data = \
534
+ (nk_##packed_value_type##_t const *)((char const *)b_packed_buffer + \
535
+ sizeof(nk_cross_packed_buffer_header_t)); \
536
+ \
537
+ /* Cache blocking parameters (no depth_block blocking - full depth accumulated per tile) */ \
538
+ nk_size_t const row_block_size = 128; /* L2 cache blocking over rows */ \
539
+ nk_size_t const column_block_size = 2048; /* L3 cache blocking over columns */ \
540
+ nk_size_t const register_row_count = 4; /* Rows per register tile */ \
541
+ nk_size_t const register_column_count = 4; /* Columns per register tile */ \
542
+ /* Correct aligned_depth calculation for sub-byte types */ \
543
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
544
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
545
+ /* Calculate step size in storage values for loop increment */ \
546
+ nk_size_t const depth_step_values = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
547
+ \
548
+ /* Zero output matrix */ \
549
+ for (nk_size_t row_index = 0; row_index < row_count; ++row_index) { \
550
+ nk_##result_value_type##_t *c_row = (nk_##result_value_type##_t *)((char *)c_matrix + \
551
+ row_index * c_stride_in_bytes); \
552
+ for (nk_size_t column_index = 0; column_index < column_count; ++column_index) c_row[column_index] = 0; \
553
+ } \
554
+ \
555
+ /* Loop 1: L3 cache blocking over columns */ \
556
+ for (nk_size_t column_block_start_index = 0; column_block_start_index < column_count; \
557
+ column_block_start_index += column_block_size) { \
558
+ nk_size_t column_block_end_index = column_block_start_index + column_block_size; \
559
+ if (column_block_end_index > column_count) column_block_end_index = column_count; \
560
+ \
561
+ /* Loop 2: L2 cache blocking over rows */ \
562
+ for (nk_size_t row_block_start_index = 0; row_block_start_index < row_count; \
563
+ row_block_start_index += row_block_size) { \
564
+ nk_size_t row_block_end_index = row_block_start_index + row_block_size; \
565
+ if (row_block_end_index > row_count) row_block_end_index = row_count; \
566
+ \
567
+ /* Loop 3: Register tiling over columns (register_column_count columns per batch) */ \
568
+ for (nk_size_t tile_column_start_index = column_block_start_index; \
569
+ tile_column_start_index < column_block_end_index; \
570
+ tile_column_start_index += register_column_count) { \
571
+ \
572
+ /* Compute B pointers once per column tile - direct column-major addressing */ \
573
+ nk_##packed_value_type##_t const *b_depth_ptr_0 = packed_data + \
574
+ (tile_column_start_index + 0) * depth_padded; \
575
+ nk_##packed_value_type##_t const *b_depth_ptr_1 = packed_data + \
576
+ (tile_column_start_index + 1) * depth_padded; \
577
+ nk_##packed_value_type##_t const *b_depth_ptr_2 = packed_data + \
578
+ (tile_column_start_index + 2) * depth_padded; \
579
+ nk_##packed_value_type##_t const *b_depth_ptr_3 = packed_data + \
580
+ (tile_column_start_index + 3) * depth_padded; \
581
+ \
582
+ /* Loop 4: Register tiling over rows (register_row_count rows per tile) */ \
583
+ for (nk_size_t tile_row_start_index = row_block_start_index; \
584
+ tile_row_start_index < row_block_end_index; tile_row_start_index += register_row_count) { \
585
+ \
586
+ /* Initialize register_row_count × register_column_count accumulator states */ \
587
+ state_type accumulator_tiles[4][4]; \
588
+ init_accumulator_fn(&accumulator_tiles[0][0]), init_accumulator_fn(&accumulator_tiles[0][1]), \
589
+ init_accumulator_fn(&accumulator_tiles[0][2]), \
590
+ init_accumulator_fn(&accumulator_tiles[0][3]); \
591
+ init_accumulator_fn(&accumulator_tiles[1][0]), init_accumulator_fn(&accumulator_tiles[1][1]), \
592
+ init_accumulator_fn(&accumulator_tiles[1][2]), \
593
+ init_accumulator_fn(&accumulator_tiles[1][3]); \
594
+ init_accumulator_fn(&accumulator_tiles[2][0]), init_accumulator_fn(&accumulator_tiles[2][1]), \
595
+ init_accumulator_fn(&accumulator_tiles[2][2]), \
596
+ init_accumulator_fn(&accumulator_tiles[2][3]); \
597
+ init_accumulator_fn(&accumulator_tiles[3][0]), init_accumulator_fn(&accumulator_tiles[3][1]), \
598
+ init_accumulator_fn(&accumulator_tiles[3][2]), \
599
+ init_accumulator_fn(&accumulator_tiles[3][3]); \
600
+ \
601
+ /* A row pointers */ \
602
+ nk_##input_value_type##_t const *a_row_ptr_0 = \
603
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
604
+ (tile_row_start_index + 0) * a_stride_in_bytes); \
605
+ nk_##input_value_type##_t const *a_row_ptr_1 = \
606
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
607
+ (tile_row_start_index + 1) * a_stride_in_bytes); \
608
+ nk_##input_value_type##_t const *a_row_ptr_2 = \
609
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
610
+ (tile_row_start_index + 2) * a_stride_in_bytes); \
611
+ nk_##input_value_type##_t const *a_row_ptr_3 = \
612
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
613
+ (tile_row_start_index + 3) * a_stride_in_bytes); \
614
+ \
615
+ /* Tight inner loop: full depth with simple depth_index addressing */ \
616
+ vec_type a_vector_0, a_vector_1, a_vector_2, a_vector_3; \
617
+ vec_type b_vector_0, b_vector_1, b_vector_2, b_vector_3; \
618
+ for (nk_size_t depth_index = 0; depth_index < aligned_depth; \
619
+ depth_index += depth_step_values) { \
620
+ /* Load next few values from 4 rows from A (unpacked, may upcast) */ \
621
+ load_a_vec_fn(a_row_ptr_0 + depth_index, &a_vector_0); \
622
+ load_a_vec_fn(a_row_ptr_1 + depth_index, &a_vector_1); \
623
+ load_a_vec_fn(a_row_ptr_2 + depth_index, &a_vector_2); \
624
+ load_a_vec_fn(a_row_ptr_3 + depth_index, &a_vector_3); \
625
+ \
626
+ /* Load next few values from 4 rows from B (packed, already upcasted) */ \
627
+ load_b_vec_fn(b_depth_ptr_0 + depth_index, &b_vector_0); \
628
+ load_b_vec_fn(b_depth_ptr_1 + depth_index, &b_vector_1); \
629
+ load_b_vec_fn(b_depth_ptr_2 + depth_index, &b_vector_2); \
630
+ load_b_vec_fn(b_depth_ptr_3 + depth_index, &b_vector_3); \
631
+ \
632
+ /* 16 FMAs: 4 A rows × 4 B columns */ \
633
+ inner_product_fn(&accumulator_tiles[0][0], a_vector_0, b_vector_0, \
634
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
635
+ inner_product_fn(&accumulator_tiles[0][1], a_vector_0, b_vector_1, \
636
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
637
+ inner_product_fn(&accumulator_tiles[0][2], a_vector_0, b_vector_2, \
638
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
639
+ inner_product_fn(&accumulator_tiles[0][3], a_vector_0, b_vector_3, \
640
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
641
+ inner_product_fn(&accumulator_tiles[1][0], a_vector_1, b_vector_0, \
642
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
643
+ inner_product_fn(&accumulator_tiles[1][1], a_vector_1, b_vector_1, \
644
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
645
+ inner_product_fn(&accumulator_tiles[1][2], a_vector_1, b_vector_2, \
646
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
647
+ inner_product_fn(&accumulator_tiles[1][3], a_vector_1, b_vector_3, \
648
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
649
+ inner_product_fn(&accumulator_tiles[2][0], a_vector_2, b_vector_0, \
650
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
651
+ inner_product_fn(&accumulator_tiles[2][1], a_vector_2, b_vector_1, \
652
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
653
+ inner_product_fn(&accumulator_tiles[2][2], a_vector_2, b_vector_2, \
654
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
655
+ inner_product_fn(&accumulator_tiles[2][3], a_vector_2, b_vector_3, \
656
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
657
+ inner_product_fn(&accumulator_tiles[3][0], a_vector_3, b_vector_0, \
658
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
659
+ inner_product_fn(&accumulator_tiles[3][1], a_vector_3, b_vector_1, \
660
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
661
+ inner_product_fn(&accumulator_tiles[3][2], a_vector_3, b_vector_2, \
662
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
663
+ inner_product_fn(&accumulator_tiles[3][3], a_vector_3, b_vector_3, \
664
+ depth_index * dimensions_per_value, depth_simd_dimensions); \
665
+ } \
666
+ /* Finalize and store register_rows x register_cols results using batched 4-way reduction */ \
667
+ result_vec_type result_vector; \
668
+ nk_##result_value_type##_t *c_row_ptr_0 = \
669
+ (nk_##result_value_type##_t *)((char *)c_matrix + \
670
+ (tile_row_start_index + 0) * c_stride_in_bytes); \
671
+ reduce_accumulators_fn(&accumulator_tiles[0][0], &accumulator_tiles[0][1], \
672
+ &accumulator_tiles[0][2], &accumulator_tiles[0][3], depth, \
673
+ &result_vector); \
674
+ store_fn(&result_vector, c_row_ptr_0 + tile_column_start_index); \
675
+ nk_##result_value_type##_t *c_row_ptr_1 = \
676
+ (nk_##result_value_type##_t *)((char *)c_matrix + \
677
+ (tile_row_start_index + 1) * c_stride_in_bytes); \
678
+ reduce_accumulators_fn(&accumulator_tiles[1][0], &accumulator_tiles[1][1], \
679
+ &accumulator_tiles[1][2], &accumulator_tiles[1][3], depth, \
680
+ &result_vector); \
681
+ store_fn(&result_vector, c_row_ptr_1 + tile_column_start_index); \
682
+ nk_##result_value_type##_t *c_row_ptr_2 = \
683
+ (nk_##result_value_type##_t *)((char *)c_matrix + \
684
+ (tile_row_start_index + 2) * c_stride_in_bytes); \
685
+ reduce_accumulators_fn(&accumulator_tiles[2][0], &accumulator_tiles[2][1], \
686
+ &accumulator_tiles[2][2], &accumulator_tiles[2][3], depth, \
687
+ &result_vector); \
688
+ store_fn(&result_vector, c_row_ptr_2 + tile_column_start_index); \
689
+ nk_##result_value_type##_t *c_row_ptr_3 = \
690
+ (nk_##result_value_type##_t *)((char *)c_matrix + \
691
+ (tile_row_start_index + 3) * c_stride_in_bytes); \
692
+ reduce_accumulators_fn(&accumulator_tiles[3][0], &accumulator_tiles[3][1], \
693
+ &accumulator_tiles[3][2], &accumulator_tiles[3][3], depth, \
694
+ &result_vector); \
695
+ store_fn(&result_vector, c_row_ptr_3 + tile_column_start_index); \
696
+ } \
697
+ } \
698
+ } \
699
+ } \
700
+ } \
701
+ NK_PUBLIC void nk_##api_name##_packed_##input_type_name##_##isa_suffix##_1x8_aligned_( \
702
+ nk_##input_value_type##_t const *a_matrix, void const *b_packed_buffer, nk_##result_value_type##_t *c_matrix, \
703
+ nk_size_t row_count, nk_size_t column_count, nk_size_t depth, nk_size_t a_stride_in_bytes, \
704
+ nk_size_t c_stride_in_bytes) { \
705
+ /* Read padded depth from header for correct stride calculation */ \
706
+ nk_cross_packed_buffer_header_t const *header = (nk_cross_packed_buffer_header_t const *)b_packed_buffer; \
707
+ nk_size_t const depth_padded = header->depth_padded_values; /* in storage values */ \
708
+ \
709
+ nk_##packed_value_type##_t const *packed_data = \
710
+ (nk_##packed_value_type##_t const *)((char const *)b_packed_buffer + \
711
+ sizeof(nk_cross_packed_buffer_header_t)); \
712
+ \
713
+ /* Cache blocking parameters (no depth_block blocking - full depth accumulated per tile) */ \
714
+ nk_size_t const row_block_size = 128; /* L2 cache blocking over rows */ \
715
+ nk_size_t const column_block_size = 2048; /* L3 cache blocking over columns */ \
716
+ nk_size_t const register_row_count = 1; /* Rows per register tile */ \
717
+ nk_size_t const register_column_count = 8; /* Columns per register tile (2 × 4) */ \
718
+ /* Correct aligned_depth calculation for sub-byte types */ \
719
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
720
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
721
+ /* Calculate step size in storage values for loop increment */ \
722
+ nk_size_t const depth_step_values = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
723
+ nk_unused_(register_row_count); /* Used in comments, loop uses 1 directly */ \
724
+ \
725
+ /* Zero output matrix */ \
726
+ for (nk_size_t row_index = 0; row_index < row_count; ++row_index) { \
727
+ nk_##result_value_type##_t *c_row = (nk_##result_value_type##_t *)((char *)c_matrix + \
728
+ row_index * c_stride_in_bytes); \
729
+ for (nk_size_t column_index = 0; column_index < column_count; ++column_index) c_row[column_index] = 0; \
730
+ } \
731
+ \
732
+ /* Loop 1: L3 cache blocking over columns */ \
733
+ for (nk_size_t column_block_start_index = 0; column_block_start_index < column_count; \
734
+ column_block_start_index += column_block_size) { \
735
+ nk_size_t column_block_end_index = column_block_start_index + column_block_size; \
736
+ if (column_block_end_index > column_count) column_block_end_index = column_count; \
737
+ \
738
+ /* Loop 2: L2 cache blocking over rows */ \
739
+ for (nk_size_t row_block_start_index = 0; row_block_start_index < row_count; \
740
+ row_block_start_index += row_block_size) { \
741
+ nk_size_t const row_block_end_index = row_block_start_index + row_block_size < row_count \
742
+ ? row_block_start_index + row_block_size \
743
+ : row_count; \
744
+ \
745
+ /* Loop 3: Register tiling over columns (register_column_count columns per batch) */ \
746
+ for (nk_size_t tile_column_start_index = column_block_start_index; \
747
+ tile_column_start_index < column_block_end_index; \
748
+ tile_column_start_index += register_column_count) { \
749
+ \
750
+ /* Compute B pointers once per column tile - direct column-major addressing */ \
751
+ nk_##packed_value_type##_t const *b_depth_ptr_0 = packed_data + \
752
+ (tile_column_start_index + 0) * depth_padded; \
753
+ nk_##packed_value_type##_t const *b_depth_ptr_1 = packed_data + \
754
+ (tile_column_start_index + 1) * depth_padded; \
755
+ nk_##packed_value_type##_t const *b_depth_ptr_2 = packed_data + \
756
+ (tile_column_start_index + 2) * depth_padded; \
757
+ nk_##packed_value_type##_t const *b_depth_ptr_3 = packed_data + \
758
+ (tile_column_start_index + 3) * depth_padded; \
759
+ nk_##packed_value_type##_t const *b_depth_ptr_4 = packed_data + \
760
+ (tile_column_start_index + 4) * depth_padded; \
761
+ nk_##packed_value_type##_t const *b_depth_ptr_5 = packed_data + \
762
+ (tile_column_start_index + 5) * depth_padded; \
763
+ nk_##packed_value_type##_t const *b_depth_ptr_6 = packed_data + \
764
+ (tile_column_start_index + 6) * depth_padded; \
765
+ nk_##packed_value_type##_t const *b_depth_ptr_7 = packed_data + \
766
+ (tile_column_start_index + 7) * depth_padded; \
767
+ \
768
+ /* Loop 4: Process 1 row at a time */ \
769
+ for (nk_size_t row_index = row_block_start_index; row_index < row_block_end_index; ++row_index) { \
770
+ \
771
+ /* Initialize 1 × 8 accumulator states */ \
772
+ state_type accumulator_0, accumulator_1, accumulator_2, accumulator_3, accumulator_4, \
773
+ accumulator_5, accumulator_6, accumulator_7; \
774
+ init_accumulator_fn(&accumulator_0), init_accumulator_fn(&accumulator_1), \
775
+ init_accumulator_fn(&accumulator_2), init_accumulator_fn(&accumulator_3), \
776
+ init_accumulator_fn(&accumulator_4), init_accumulator_fn(&accumulator_5), \
777
+ init_accumulator_fn(&accumulator_6), init_accumulator_fn(&accumulator_7); \
778
+ \
779
+ /* A row pointer */ \
780
+ nk_##input_value_type##_t const *a_row_ptr = \
781
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
782
+ row_index * a_stride_in_bytes); \
783
+ \
784
+ /* Tight inner loop: full depth with simple depth_index addressing */ \
785
+ vec_type a_vector; \
786
+ vec_type b_vector_0, b_vector_1, b_vector_2, b_vector_3, b_vector_4, b_vector_5, b_vector_6, \
787
+ b_vector_7; \
788
+ for (nk_size_t depth_index = 0; depth_index < aligned_depth; \
789
+ depth_index += depth_step_values) { \
790
+ /* Load A vector (1 row) */ \
791
+ load_a_vec_fn(a_row_ptr + depth_index, &a_vector); \
792
+ \
793
+ /* Load B vectors (8 columns) */ \
794
+ load_b_vec_fn(b_depth_ptr_0 + depth_index, &b_vector_0); \
795
+ load_b_vec_fn(b_depth_ptr_1 + depth_index, &b_vector_1); \
796
+ load_b_vec_fn(b_depth_ptr_2 + depth_index, &b_vector_2); \
797
+ load_b_vec_fn(b_depth_ptr_3 + depth_index, &b_vector_3); \
798
+ load_b_vec_fn(b_depth_ptr_4 + depth_index, &b_vector_4); \
799
+ load_b_vec_fn(b_depth_ptr_5 + depth_index, &b_vector_5); \
800
+ load_b_vec_fn(b_depth_ptr_6 + depth_index, &b_vector_6); \
801
+ load_b_vec_fn(b_depth_ptr_7 + depth_index, &b_vector_7); \
802
+ \
803
+ /* 8 FMAs: 1 A row × 8 B columns */ \
804
+ inner_product_fn(&accumulator_0, a_vector, b_vector_0, depth_index * dimensions_per_value, \
805
+ depth_simd_dimensions); \
806
+ inner_product_fn(&accumulator_1, a_vector, b_vector_1, depth_index * dimensions_per_value, \
807
+ depth_simd_dimensions); \
808
+ inner_product_fn(&accumulator_2, a_vector, b_vector_2, depth_index * dimensions_per_value, \
809
+ depth_simd_dimensions); \
810
+ inner_product_fn(&accumulator_3, a_vector, b_vector_3, depth_index * dimensions_per_value, \
811
+ depth_simd_dimensions); \
812
+ inner_product_fn(&accumulator_4, a_vector, b_vector_4, depth_index * dimensions_per_value, \
813
+ depth_simd_dimensions); \
814
+ inner_product_fn(&accumulator_5, a_vector, b_vector_5, depth_index * dimensions_per_value, \
815
+ depth_simd_dimensions); \
816
+ inner_product_fn(&accumulator_6, a_vector, b_vector_6, depth_index * dimensions_per_value, \
817
+ depth_simd_dimensions); \
818
+ inner_product_fn(&accumulator_7, a_vector, b_vector_7, depth_index * dimensions_per_value, \
819
+ depth_simd_dimensions); \
820
+ } \
821
+ \
822
+ /* Finalize and store 1 × 8 results using two 4-way reductions */ \
823
+ result_vec_type result_vector; \
824
+ nk_##result_value_type##_t *c_row_ptr = \
825
+ (nk_##result_value_type##_t *)((char *)c_matrix + row_index * c_stride_in_bytes); \
826
+ /* First 4 columns */ \
827
+ reduce_accumulators_fn(&accumulator_0, &accumulator_1, &accumulator_2, &accumulator_3, depth, \
828
+ &result_vector); \
829
+ store_fn(&result_vector, c_row_ptr + tile_column_start_index); \
830
+ /* Second 4 columns */ \
831
+ reduce_accumulators_fn(&accumulator_4, &accumulator_5, &accumulator_6, &accumulator_7, depth, \
832
+ &result_vector); \
833
+ store_fn(&result_vector, c_row_ptr + tile_column_start_index + 4); \
834
+ } \
835
+ } \
836
+ } \
837
+ } \
838
+ } \
839
+ NK_PUBLIC void nk_##api_name##_packed_##input_type_name##_##isa_suffix( \
840
+ nk_##input_value_type##_t const *a_matrix, void const *b_packed_buffer, nk_##result_value_type##_t *c_matrix, \
841
+ nk_size_t row_count, nk_size_t column_count, nk_size_t depth, nk_size_t a_stride_in_bytes, \
842
+ nk_size_t c_stride_in_bytes) { \
843
+ /* Read padded depth from header for correct stride calculation */ \
844
+ nk_cross_packed_buffer_header_t const *header = (nk_cross_packed_buffer_header_t const *)b_packed_buffer; \
845
+ nk_size_t const depth_padded = header->depth_padded_values; \
846
+ \
847
+ /* Cache blocking parameters (hardcoded for optimal L1/L2/L3 utilization) */ \
848
+ nk_size_t const row_block_size = 128; /* L2 cache blocking over rows */ \
849
+ nk_size_t const column_block_size = 2048; /* L3 cache blocking over columns */ \
850
+ nk_size_t const register_row_count = 4; /* Rows per register tile */ \
851
+ nk_size_t const register_column_count = 4; /* Columns per register tile */ \
852
+ nk_unused_(register_column_count); /* Suppress unused warnings */ \
853
+ /* Use 1 × 8 kernel when columns are aligned to 8 and many columns relative to rows */ \
854
+ if (column_count % 8 == 0 && column_count >= row_count * 2 && depth % depth_simd_dimensions == 0) { \
855
+ nk_##api_name##_packed_##input_type_name##_##isa_suffix##_1x8_aligned_( \
856
+ a_matrix, b_packed_buffer, c_matrix, row_count, column_count, depth, a_stride_in_bytes, \
857
+ c_stride_in_bytes); \
858
+ return; \
859
+ } \
860
+ /* Use 4 × 4 kernel when dimensions are 4-aligned */ \
861
+ if (row_count % 4 == 0 && column_count % 4 == 0 && depth % depth_simd_dimensions == 0) { \
862
+ nk_##api_name##_packed_##input_type_name##_##isa_suffix##_aligned_(a_matrix, b_packed_buffer, c_matrix, \
863
+ row_count, column_count, depth, \
864
+ a_stride_in_bytes, c_stride_in_bytes); \
865
+ return; \
866
+ } \
867
+ \
868
+ /* Zero output matrix */ \
869
+ for (nk_size_t row_index = 0; row_index < row_count; ++row_index) { \
870
+ nk_##result_value_type##_t *c_row = (nk_##result_value_type##_t *)((char *)c_matrix + \
871
+ row_index * c_stride_in_bytes); \
872
+ for (nk_size_t column_index = 0; column_index < column_count; ++column_index) c_row[column_index] = 0; \
873
+ } \
874
+ \
875
+ /* Compute aligned/remainder depth for partial loads (correct for sub-byte types) */ \
876
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
877
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
878
+ nk_size_t const depth_in_values = nk_size_divide_round_up_(depth, dimensions_per_value); \
879
+ nk_size_t const remainder_depth = depth_in_values - aligned_depth; \
880
+ nk_size_t const remainder_dimensions = depth - depth_dimensions_aligned; \
881
+ /* Calculate step size in storage values for loop increment */ \
882
+ nk_size_t const depth_step_values = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
883
+ \
884
+ /* Loop 1: L3 cache blocking over columns */ \
885
+ nk_##packed_value_type##_t const *packed_data = \
886
+ (nk_##packed_value_type##_t const *)((char const *)b_packed_buffer + \
887
+ sizeof(nk_cross_packed_buffer_header_t)); \
888
+ for (nk_size_t column_block_start_index = 0; column_block_start_index < column_count; \
889
+ column_block_start_index += column_block_size) { \
890
+ nk_size_t column_block_end_index = column_block_start_index + column_block_size; \
891
+ if (column_block_end_index > column_count) column_block_end_index = column_count; \
892
+ \
893
+ /* Loop 2: L2 cache blocking over rows */ \
894
+ for (nk_size_t row_block_start_index = 0; row_block_start_index < row_count; \
895
+ row_block_start_index += row_block_size) { \
896
+ nk_size_t row_block_end_index = row_block_start_index + row_block_size; \
897
+ if (row_block_end_index > row_count) row_block_end_index = row_count; \
898
+ \
899
+ /* Loop 4: Register tiling over columns (register_column_count columns per batch) */ \
900
+ for (nk_size_t tile_column_start_index = column_block_start_index; \
901
+ tile_column_start_index < column_block_end_index; \
902
+ tile_column_start_index += register_column_count) { \
903
+ nk_size_t tile_column_count = register_column_count; \
904
+ if (tile_column_start_index + tile_column_count > column_block_end_index) \
905
+ tile_column_count = column_block_end_index - tile_column_start_index; \
906
+ \
907
+ /* Compute B pointers once per column tile - direct column-major addressing */ \
908
+ nk_##packed_value_type##_t const *b_depth_ptr_0 = packed_data + \
909
+ (tile_column_start_index + 0) * depth_padded; \
910
+ nk_##packed_value_type##_t const *b_depth_ptr_1 = \
911
+ (tile_column_count > 1) ? packed_data + (tile_column_start_index + 1) * depth_padded \
912
+ : b_depth_ptr_0; \
913
+ nk_##packed_value_type##_t const *b_depth_ptr_2 = \
914
+ (tile_column_count > 2) ? packed_data + (tile_column_start_index + 2) * depth_padded \
915
+ : b_depth_ptr_0; \
916
+ nk_##packed_value_type##_t const *b_depth_ptr_3 = \
917
+ (tile_column_count > 3) ? packed_data + (tile_column_start_index + 3) * depth_padded \
918
+ : b_depth_ptr_0; \
919
+ \
920
+ /* Loop 5: Register tiling over rows (register_rows rows per tile) */ \
921
+ for (nk_size_t tile_row_start_index = row_block_start_index; \
922
+ tile_row_start_index < row_block_end_index; tile_row_start_index += register_row_count) { \
923
+ nk_size_t tile_row_count = register_row_count; \
924
+ if (tile_row_start_index + tile_row_count > row_block_end_index) \
925
+ tile_row_count = row_block_end_index - tile_row_start_index; \
926
+ \
927
+ /* Initialize register_rows x register_cols accumulator states */ \
928
+ state_type accumulator_tiles[4][4]; \
929
+ for (nk_size_t r = 0; r < tile_row_count; ++r) { \
930
+ init_accumulator_fn(&accumulator_tiles[r][0]); \
931
+ init_accumulator_fn(&accumulator_tiles[r][1]); \
932
+ init_accumulator_fn(&accumulator_tiles[r][2]); \
933
+ init_accumulator_fn(&accumulator_tiles[r][3]); \
934
+ } \
935
+ \
936
+ /* A row pointers */ \
937
+ nk_##input_value_type##_t const *a_row_ptr_0 = \
938
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
939
+ (tile_row_start_index + 0) * a_stride_in_bytes); \
940
+ nk_##input_value_type##_t const *a_row_ptr_1 = \
941
+ (tile_row_count > 1) \
942
+ ? (nk_##input_value_type##_t const *)((char const *)a_matrix + \
943
+ (tile_row_start_index + 1) * a_stride_in_bytes) \
944
+ : a_row_ptr_0; \
945
+ nk_##input_value_type##_t const *a_row_ptr_2 = \
946
+ (tile_row_count > 2) \
947
+ ? (nk_##input_value_type##_t const *)((char const *)a_matrix + \
948
+ (tile_row_start_index + 2) * a_stride_in_bytes) \
949
+ : a_row_ptr_0; \
950
+ nk_##input_value_type##_t const *a_row_ptr_3 = \
951
+ (tile_row_count > 3) \
952
+ ? (nk_##input_value_type##_t const *)((char const *)a_matrix + \
953
+ (tile_row_start_index + 3) * a_stride_in_bytes) \
954
+ : a_row_ptr_0; \
955
+ \
956
+ /* Tight inner loop: k values with simple ptr+k addressing */ \
957
+ vec_type a_first_vec, a_second_vec, a_third_vec, a_fourth_vec; \
958
+ vec_type b_first_vec, b_second_vec, b_third_vec, b_fourth_vec; \
959
+ for (nk_size_t k = 0; k < aligned_depth; k += depth_step_values) { \
960
+ /* Load next few values from 4 rows from A */ \
961
+ load_a_vec_fn(a_row_ptr_0 + k, &a_first_vec); \
962
+ load_a_vec_fn(a_row_ptr_1 + k, &a_second_vec); \
963
+ load_a_vec_fn(a_row_ptr_2 + k, &a_third_vec); \
964
+ load_a_vec_fn(a_row_ptr_3 + k, &a_fourth_vec); \
965
+ \
966
+ /* Load next few values from 4 rows from B */ \
967
+ load_b_vec_fn(b_depth_ptr_0 + k, &b_first_vec); \
968
+ load_b_vec_fn(b_depth_ptr_1 + k, &b_second_vec); \
969
+ load_b_vec_fn(b_depth_ptr_2 + k, &b_third_vec); \
970
+ load_b_vec_fn(b_depth_ptr_3 + k, &b_fourth_vec); \
971
+ \
972
+ /* 16 FMAs: 4 A rows × 4 B columns */ \
973
+ inner_product_fn(&accumulator_tiles[0][0], a_first_vec, b_first_vec, \
974
+ k * dimensions_per_value, depth_simd_dimensions); \
975
+ inner_product_fn(&accumulator_tiles[0][1], a_first_vec, b_second_vec, \
976
+ k * dimensions_per_value, depth_simd_dimensions); \
977
+ inner_product_fn(&accumulator_tiles[0][2], a_first_vec, b_third_vec, \
978
+ k * dimensions_per_value, depth_simd_dimensions); \
979
+ inner_product_fn(&accumulator_tiles[0][3], a_first_vec, b_fourth_vec, \
980
+ k * dimensions_per_value, depth_simd_dimensions); \
981
+ inner_product_fn(&accumulator_tiles[1][0], a_second_vec, b_first_vec, \
982
+ k * dimensions_per_value, depth_simd_dimensions); \
983
+ inner_product_fn(&accumulator_tiles[1][1], a_second_vec, b_second_vec, \
984
+ k * dimensions_per_value, depth_simd_dimensions); \
985
+ inner_product_fn(&accumulator_tiles[1][2], a_second_vec, b_third_vec, \
986
+ k * dimensions_per_value, depth_simd_dimensions); \
987
+ inner_product_fn(&accumulator_tiles[1][3], a_second_vec, b_fourth_vec, \
988
+ k * dimensions_per_value, depth_simd_dimensions); \
989
+ inner_product_fn(&accumulator_tiles[2][0], a_third_vec, b_first_vec, \
990
+ k * dimensions_per_value, depth_simd_dimensions); \
991
+ inner_product_fn(&accumulator_tiles[2][1], a_third_vec, b_second_vec, \
992
+ k * dimensions_per_value, depth_simd_dimensions); \
993
+ inner_product_fn(&accumulator_tiles[2][2], a_third_vec, b_third_vec, \
994
+ k * dimensions_per_value, depth_simd_dimensions); \
995
+ inner_product_fn(&accumulator_tiles[2][3], a_third_vec, b_fourth_vec, \
996
+ k * dimensions_per_value, depth_simd_dimensions); \
997
+ inner_product_fn(&accumulator_tiles[3][0], a_fourth_vec, b_first_vec, \
998
+ k * dimensions_per_value, depth_simd_dimensions); \
999
+ inner_product_fn(&accumulator_tiles[3][1], a_fourth_vec, b_second_vec, \
1000
+ k * dimensions_per_value, depth_simd_dimensions); \
1001
+ inner_product_fn(&accumulator_tiles[3][2], a_fourth_vec, b_third_vec, \
1002
+ k * dimensions_per_value, depth_simd_dimensions); \
1003
+ inner_product_fn(&accumulator_tiles[3][3], a_fourth_vec, b_fourth_vec, \
1004
+ k * dimensions_per_value, depth_simd_dimensions); \
1005
+ } \
1006
+ \
1007
+ /* Handle remainder k positions with partial loads */ \
1008
+ if (remainder_depth > 0) { \
1009
+ /* Load next few values from 4 rows from A */ \
1010
+ partial_load_a_vec_fn(a_row_ptr_0 + aligned_depth, &a_first_vec, remainder_dimensions); \
1011
+ partial_load_a_vec_fn(a_row_ptr_1 + aligned_depth, &a_second_vec, remainder_dimensions); \
1012
+ partial_load_a_vec_fn(a_row_ptr_2 + aligned_depth, &a_third_vec, remainder_dimensions); \
1013
+ partial_load_a_vec_fn(a_row_ptr_3 + aligned_depth, &a_fourth_vec, remainder_dimensions); \
1014
+ \
1015
+ /* Load next few values from 4 rows from B */ \
1016
+ partial_load_b_vec_fn(b_depth_ptr_0 + aligned_depth, &b_first_vec, remainder_dimensions); \
1017
+ partial_load_b_vec_fn(b_depth_ptr_1 + aligned_depth, &b_second_vec, remainder_dimensions); \
1018
+ partial_load_b_vec_fn(b_depth_ptr_2 + aligned_depth, &b_third_vec, remainder_dimensions); \
1019
+ partial_load_b_vec_fn(b_depth_ptr_3 + aligned_depth, &b_fourth_vec, remainder_dimensions); \
1020
+ \
1021
+ /* 16 FMAs: 4 A rows × 4 B columns */ \
1022
+ inner_product_fn(&accumulator_tiles[0][0], a_first_vec, b_first_vec, \
1023
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1024
+ inner_product_fn(&accumulator_tiles[0][1], a_first_vec, b_second_vec, \
1025
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1026
+ inner_product_fn(&accumulator_tiles[0][2], a_first_vec, b_third_vec, \
1027
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1028
+ inner_product_fn(&accumulator_tiles[0][3], a_first_vec, b_fourth_vec, \
1029
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1030
+ inner_product_fn(&accumulator_tiles[1][0], a_second_vec, b_first_vec, \
1031
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1032
+ inner_product_fn(&accumulator_tiles[1][1], a_second_vec, b_second_vec, \
1033
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1034
+ inner_product_fn(&accumulator_tiles[1][2], a_second_vec, b_third_vec, \
1035
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1036
+ inner_product_fn(&accumulator_tiles[1][3], a_second_vec, b_fourth_vec, \
1037
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1038
+ inner_product_fn(&accumulator_tiles[2][0], a_third_vec, b_first_vec, \
1039
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1040
+ inner_product_fn(&accumulator_tiles[2][1], a_third_vec, b_second_vec, \
1041
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1042
+ inner_product_fn(&accumulator_tiles[2][2], a_third_vec, b_third_vec, \
1043
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1044
+ inner_product_fn(&accumulator_tiles[2][3], a_third_vec, b_fourth_vec, \
1045
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1046
+ inner_product_fn(&accumulator_tiles[3][0], a_fourth_vec, b_first_vec, \
1047
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1048
+ inner_product_fn(&accumulator_tiles[3][1], a_fourth_vec, b_second_vec, \
1049
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1050
+ inner_product_fn(&accumulator_tiles[3][2], a_fourth_vec, b_third_vec, \
1051
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1052
+ inner_product_fn(&accumulator_tiles[3][3], a_fourth_vec, b_fourth_vec, \
1053
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1054
+ } \
1055
+ \
1056
+ /* Finalize and store register_rows x register_cols results using batched 4-way reduction */ \
1057
+ for (nk_size_t r = 0; r < tile_row_count; ++r) { \
1058
+ result_vec_type result_vector; \
1059
+ reduce_accumulators_fn(&accumulator_tiles[r][0], &accumulator_tiles[r][1], \
1060
+ &accumulator_tiles[r][2], &accumulator_tiles[r][3], depth, \
1061
+ &result_vector); \
1062
+ \
1063
+ nk_##result_value_type##_t *c_row = \
1064
+ (nk_##result_value_type##_t *)((char *)c_matrix + \
1065
+ (tile_row_start_index + r) * c_stride_in_bytes); \
1066
+ partial_store_fn(&result_vector, c_row + tile_column_start_index, tile_column_count); \
1067
+ } \
1068
+ } \
1069
+ } \
1070
+ } \
1071
+ } \
1072
+ }
1073
+
1074
+ /**
1075
+ * @brief Generates compensated GEMM: C = A × Bᵀ with precomputed B column sums.
1076
+ *
1077
+ * Like nk_define_cross_packed_ but the finalize function receives precomputed B column sums
1078
+ * and per-row A sums to apply algebraic correction inline. This eliminates correction
1079
+ * accumulators from the inner loop state, halving register pressure for integer dot products.
1080
+ *
1081
+ * The compensated_finalize_fn signature differs from the standard reduce_accumulators_fn:
1082
+ * compensated_finalize_fn(state_a, state_b, state_c, state_d, depth, a_sum, b_sums_vec, result)
1083
+ * where a_sum is a scalar A row sum and b_sums_vec contains 4 B column sums as SIMD vector.
1084
+ *
1085
+ * Buffer layout: [ Header ] [ Packed data ] [ Norms ] [ Column sums ]
1086
+ * The norms occupy the same position as in non-compensated packs, so spatial functions work.
1087
+ */
1088
+ #define nk_define_cross_compensated_packed_( \
1089
+ api_name, input_type_name, isa_suffix, input_value_type, packed_value_type, result_value_type, sum_value_type, \
1090
+ norm_value_type, vec_type, state_type, result_vec_type, init_accumulator_fn, load_a_vec_fn, partial_load_a_vec_fn, \
1091
+ load_b_vec_fn, partial_load_b_vec_fn, inner_product_fn, compensated_finalize_fn, store_fn, partial_store_fn, \
1092
+ load_sum_fn, partial_load_sum_fn, compute_a_sum_fn, depth_simd_dimensions, dimensions_per_value) \
1093
+ NK_PUBLIC void nk_##api_name##_packed_##input_type_name##_##isa_suffix##_aligned_( \
1094
+ nk_##input_value_type##_t const *a_matrix, void const *b_packed_buffer, nk_##result_value_type##_t *c_matrix, \
1095
+ nk_size_t row_count, nk_size_t column_count, nk_size_t depth, nk_size_t a_stride_in_bytes, \
1096
+ nk_size_t c_stride_in_bytes) { \
1097
+ nk_cross_packed_buffer_header_t const *header = (nk_cross_packed_buffer_header_t const *)b_packed_buffer; \
1098
+ nk_size_t const depth_padded = header->depth_padded_values; \
1099
+ nk_##packed_value_type##_t const *packed_data = \
1100
+ (nk_##packed_value_type##_t const *)((char const *)b_packed_buffer + \
1101
+ sizeof(nk_cross_packed_buffer_header_t)); \
1102
+ /* Locate column sums: after packed data + norms */ \
1103
+ nk_size_t const total_packed_values = column_count * depth_padded; \
1104
+ nk_##norm_value_type##_t const *b_norms = (nk_##norm_value_type##_t const *)(packed_data + \
1105
+ total_packed_values); \
1106
+ nk_##sum_value_type##_t const *b_sums = (nk_##sum_value_type##_t const *)(b_norms + column_count); \
1107
+ nk_unused_(b_norms); \
1108
+ nk_size_t const row_block_size = 128; \
1109
+ nk_size_t const column_block_size = 2048; \
1110
+ nk_size_t const register_row_count = 4; \
1111
+ nk_size_t const register_column_count = 4; \
1112
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
1113
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
1114
+ nk_size_t const depth_step_values = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
1115
+ for (nk_size_t row_index = 0; row_index < row_count; ++row_index) { \
1116
+ nk_##result_value_type##_t *c_row = (nk_##result_value_type##_t *)((char *)c_matrix + \
1117
+ row_index * c_stride_in_bytes); \
1118
+ for (nk_size_t ci = 0; ci < column_count; ++ci) c_row[ci] = 0; \
1119
+ } \
1120
+ for (nk_size_t cb = 0; cb < column_count; cb += column_block_size) { \
1121
+ nk_size_t ce = cb + column_block_size; \
1122
+ if (ce > column_count) ce = column_count; \
1123
+ for (nk_size_t rb = 0; rb < row_count; rb += row_block_size) { \
1124
+ nk_size_t re = rb + row_block_size; \
1125
+ if (re > row_count) re = row_count; \
1126
+ for (nk_size_t tc = cb; tc < ce; tc += register_column_count) { \
1127
+ nk_##packed_value_type##_t const *b_depth_ptr_0 = packed_data + (tc + 0) * depth_padded; \
1128
+ nk_##packed_value_type##_t const *b_depth_ptr_1 = packed_data + (tc + 1) * depth_padded; \
1129
+ nk_##packed_value_type##_t const *b_depth_ptr_2 = packed_data + (tc + 2) * depth_padded; \
1130
+ nk_##packed_value_type##_t const *b_depth_ptr_3 = packed_data + (tc + 3) * depth_padded; \
1131
+ /* Load 4 B column sums as SIMD vector */ \
1132
+ result_vec_type b_sum_vec; \
1133
+ load_sum_fn(b_sums + tc, &b_sum_vec); \
1134
+ for (nk_size_t tr = rb; tr < re; tr += register_row_count) { \
1135
+ state_type acc[4][4]; \
1136
+ init_accumulator_fn(&acc[0][0]), init_accumulator_fn(&acc[0][1]), \
1137
+ init_accumulator_fn(&acc[0][2]), init_accumulator_fn(&acc[0][3]); \
1138
+ init_accumulator_fn(&acc[1][0]), init_accumulator_fn(&acc[1][1]), \
1139
+ init_accumulator_fn(&acc[1][2]), init_accumulator_fn(&acc[1][3]); \
1140
+ init_accumulator_fn(&acc[2][0]), init_accumulator_fn(&acc[2][1]), \
1141
+ init_accumulator_fn(&acc[2][2]), init_accumulator_fn(&acc[2][3]); \
1142
+ init_accumulator_fn(&acc[3][0]), init_accumulator_fn(&acc[3][1]), \
1143
+ init_accumulator_fn(&acc[3][2]), init_accumulator_fn(&acc[3][3]); \
1144
+ nk_##input_value_type##_t const *a_row_ptr_0 = \
1145
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1146
+ (tr + 0) * a_stride_in_bytes); \
1147
+ nk_##input_value_type##_t const *a_row_ptr_1 = \
1148
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1149
+ (tr + 1) * a_stride_in_bytes); \
1150
+ nk_##input_value_type##_t const *a_row_ptr_2 = \
1151
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1152
+ (tr + 2) * a_stride_in_bytes); \
1153
+ nk_##input_value_type##_t const *a_row_ptr_3 = \
1154
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1155
+ (tr + 3) * a_stride_in_bytes); \
1156
+ /* Precompute A row sums (no-op for i8/u8, real for i4) */ \
1157
+ nk_##sum_value_type##_t a_sums[4]; \
1158
+ a_sums[0] = compute_a_sum_fn(a_row_ptr_0, depth); \
1159
+ a_sums[1] = compute_a_sum_fn(a_row_ptr_1, depth); \
1160
+ a_sums[2] = compute_a_sum_fn(a_row_ptr_2, depth); \
1161
+ a_sums[3] = compute_a_sum_fn(a_row_ptr_3, depth); \
1162
+ vec_type av0, av1, av2, av3, bv0, bv1, bv2, bv3; \
1163
+ for (nk_size_t di = 0; di < aligned_depth; di += depth_step_values) { \
1164
+ load_a_vec_fn(a_row_ptr_0 + di, &av0); \
1165
+ load_a_vec_fn(a_row_ptr_1 + di, &av1); \
1166
+ load_a_vec_fn(a_row_ptr_2 + di, &av2); \
1167
+ load_a_vec_fn(a_row_ptr_3 + di, &av3); \
1168
+ load_b_vec_fn(b_depth_ptr_0 + di, &bv0); \
1169
+ load_b_vec_fn(b_depth_ptr_1 + di, &bv1); \
1170
+ load_b_vec_fn(b_depth_ptr_2 + di, &bv2); \
1171
+ load_b_vec_fn(b_depth_ptr_3 + di, &bv3); \
1172
+ inner_product_fn(&acc[0][0], av0, bv0, di * dimensions_per_value, depth_simd_dimensions); \
1173
+ inner_product_fn(&acc[0][1], av0, bv1, di * dimensions_per_value, depth_simd_dimensions); \
1174
+ inner_product_fn(&acc[0][2], av0, bv2, di * dimensions_per_value, depth_simd_dimensions); \
1175
+ inner_product_fn(&acc[0][3], av0, bv3, di * dimensions_per_value, depth_simd_dimensions); \
1176
+ inner_product_fn(&acc[1][0], av1, bv0, di * dimensions_per_value, depth_simd_dimensions); \
1177
+ inner_product_fn(&acc[1][1], av1, bv1, di * dimensions_per_value, depth_simd_dimensions); \
1178
+ inner_product_fn(&acc[1][2], av1, bv2, di * dimensions_per_value, depth_simd_dimensions); \
1179
+ inner_product_fn(&acc[1][3], av1, bv3, di * dimensions_per_value, depth_simd_dimensions); \
1180
+ inner_product_fn(&acc[2][0], av2, bv0, di * dimensions_per_value, depth_simd_dimensions); \
1181
+ inner_product_fn(&acc[2][1], av2, bv1, di * dimensions_per_value, depth_simd_dimensions); \
1182
+ inner_product_fn(&acc[2][2], av2, bv2, di * dimensions_per_value, depth_simd_dimensions); \
1183
+ inner_product_fn(&acc[2][3], av2, bv3, di * dimensions_per_value, depth_simd_dimensions); \
1184
+ inner_product_fn(&acc[3][0], av3, bv0, di * dimensions_per_value, depth_simd_dimensions); \
1185
+ inner_product_fn(&acc[3][1], av3, bv1, di * dimensions_per_value, depth_simd_dimensions); \
1186
+ inner_product_fn(&acc[3][2], av3, bv2, di * dimensions_per_value, depth_simd_dimensions); \
1187
+ inner_product_fn(&acc[3][3], av3, bv3, di * dimensions_per_value, depth_simd_dimensions); \
1188
+ } \
1189
+ /* Compensated finalize: apply correction with precomputed sums */ \
1190
+ result_vec_type result_vector; \
1191
+ for (nk_size_t r = 0; r < register_row_count; ++r) { \
1192
+ compensated_finalize_fn(&acc[r][0], &acc[r][1], &acc[r][2], &acc[r][3], depth, a_sums[r], \
1193
+ b_sum_vec, &result_vector); \
1194
+ nk_##result_value_type##_t *c_row = \
1195
+ (nk_##result_value_type##_t *)((char *)c_matrix + (tr + r) * c_stride_in_bytes); \
1196
+ store_fn(&result_vector, c_row + tc); \
1197
+ } \
1198
+ } \
1199
+ } \
1200
+ } \
1201
+ } \
1202
+ } \
1203
+ NK_PUBLIC void nk_##api_name##_packed_##input_type_name##_##isa_suffix##_1x8_aligned_( \
1204
+ nk_##input_value_type##_t const *a_matrix, void const *b_packed_buffer, nk_##result_value_type##_t *c_matrix, \
1205
+ nk_size_t row_count, nk_size_t column_count, nk_size_t depth, nk_size_t a_stride_in_bytes, \
1206
+ nk_size_t c_stride_in_bytes) { \
1207
+ nk_cross_packed_buffer_header_t const *header = (nk_cross_packed_buffer_header_t const *)b_packed_buffer; \
1208
+ nk_size_t const depth_padded = header->depth_padded_values; \
1209
+ nk_##packed_value_type##_t const *packed_data = \
1210
+ (nk_##packed_value_type##_t const *)((char const *)b_packed_buffer + \
1211
+ sizeof(nk_cross_packed_buffer_header_t)); \
1212
+ nk_size_t const total_packed_values = column_count * depth_padded; \
1213
+ nk_##norm_value_type##_t const *b_norms = (nk_##norm_value_type##_t const *)(packed_data + \
1214
+ total_packed_values); \
1215
+ nk_##sum_value_type##_t const *b_sums = (nk_##sum_value_type##_t const *)(b_norms + column_count); \
1216
+ nk_unused_(b_norms); \
1217
+ nk_size_t const row_block_size = 128; \
1218
+ nk_size_t const column_block_size = 2048; \
1219
+ nk_size_t const register_column_count = 8; \
1220
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
1221
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
1222
+ nk_size_t const depth_step_values = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
1223
+ for (nk_size_t row_index = 0; row_index < row_count; ++row_index) { \
1224
+ nk_##result_value_type##_t *c_row = (nk_##result_value_type##_t *)((char *)c_matrix + \
1225
+ row_index * c_stride_in_bytes); \
1226
+ for (nk_size_t ci = 0; ci < column_count; ++ci) c_row[ci] = 0; \
1227
+ } \
1228
+ for (nk_size_t cb = 0; cb < column_count; cb += column_block_size) { \
1229
+ nk_size_t ce = cb + column_block_size; \
1230
+ if (ce > column_count) ce = column_count; \
1231
+ for (nk_size_t rb2 = 0; rb2 < row_count; rb2 += row_block_size) { \
1232
+ nk_size_t re2 = rb2 + row_block_size < row_count ? rb2 + row_block_size : row_count; \
1233
+ for (nk_size_t tc = cb; tc < ce; tc += register_column_count) { \
1234
+ nk_##packed_value_type##_t const *bp0 = packed_data + (tc + 0) * depth_padded; \
1235
+ nk_##packed_value_type##_t const *bp1 = packed_data + (tc + 1) * depth_padded; \
1236
+ nk_##packed_value_type##_t const *bp2 = packed_data + (tc + 2) * depth_padded; \
1237
+ nk_##packed_value_type##_t const *bp3 = packed_data + (tc + 3) * depth_padded; \
1238
+ nk_##packed_value_type##_t const *bp4 = packed_data + (tc + 4) * depth_padded; \
1239
+ nk_##packed_value_type##_t const *bp5 = packed_data + (tc + 5) * depth_padded; \
1240
+ nk_##packed_value_type##_t const *bp6 = packed_data + (tc + 6) * depth_padded; \
1241
+ nk_##packed_value_type##_t const *bp7 = packed_data + (tc + 7) * depth_padded; \
1242
+ result_vec_type b_sum_low, b_sum_high; \
1243
+ load_sum_fn(b_sums + tc, &b_sum_low); \
1244
+ load_sum_fn(b_sums + tc + 4, &b_sum_high); \
1245
+ for (nk_size_t ri = rb2; ri < re2; ++ri) { \
1246
+ state_type s0, s1, s2, s3, s4, s5, s6, s7; \
1247
+ init_accumulator_fn(&s0), init_accumulator_fn(&s1), init_accumulator_fn(&s2), \
1248
+ init_accumulator_fn(&s3), init_accumulator_fn(&s4), init_accumulator_fn(&s5), \
1249
+ init_accumulator_fn(&s6), init_accumulator_fn(&s7); \
1250
+ nk_##input_value_type##_t const *a_row = \
1251
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + ri * a_stride_in_bytes); \
1252
+ nk_##sum_value_type##_t a_sum_val = compute_a_sum_fn(a_row, depth); \
1253
+ vec_type av; \
1254
+ vec_type bv0, bv1, bv2, bv3, bv4, bv5, bv6, bv7; \
1255
+ for (nk_size_t di = 0; di < aligned_depth; di += depth_step_values) { \
1256
+ load_a_vec_fn(a_row + di, &av); \
1257
+ load_b_vec_fn(bp0 + di, &bv0), load_b_vec_fn(bp1 + di, &bv1); \
1258
+ load_b_vec_fn(bp2 + di, &bv2), load_b_vec_fn(bp3 + di, &bv3); \
1259
+ load_b_vec_fn(bp4 + di, &bv4), load_b_vec_fn(bp5 + di, &bv5); \
1260
+ load_b_vec_fn(bp6 + di, &bv6), load_b_vec_fn(bp7 + di, &bv7); \
1261
+ inner_product_fn(&s0, av, bv0, di * dimensions_per_value, depth_simd_dimensions); \
1262
+ inner_product_fn(&s1, av, bv1, di * dimensions_per_value, depth_simd_dimensions); \
1263
+ inner_product_fn(&s2, av, bv2, di * dimensions_per_value, depth_simd_dimensions); \
1264
+ inner_product_fn(&s3, av, bv3, di * dimensions_per_value, depth_simd_dimensions); \
1265
+ inner_product_fn(&s4, av, bv4, di * dimensions_per_value, depth_simd_dimensions); \
1266
+ inner_product_fn(&s5, av, bv5, di * dimensions_per_value, depth_simd_dimensions); \
1267
+ inner_product_fn(&s6, av, bv6, di * dimensions_per_value, depth_simd_dimensions); \
1268
+ inner_product_fn(&s7, av, bv7, di * dimensions_per_value, depth_simd_dimensions); \
1269
+ } \
1270
+ result_vec_type rv; \
1271
+ nk_##result_value_type##_t *c_row = (nk_##result_value_type##_t *)((char *)c_matrix + \
1272
+ ri * c_stride_in_bytes); \
1273
+ compensated_finalize_fn(&s0, &s1, &s2, &s3, depth, a_sum_val, b_sum_low, &rv); \
1274
+ store_fn(&rv, c_row + tc); \
1275
+ compensated_finalize_fn(&s4, &s5, &s6, &s7, depth, a_sum_val, b_sum_high, &rv); \
1276
+ store_fn(&rv, c_row + tc + 4); \
1277
+ } \
1278
+ } \
1279
+ } \
1280
+ } \
1281
+ } \
1282
+ NK_PUBLIC void nk_##api_name##_packed_##input_type_name##_##isa_suffix( \
1283
+ nk_##input_value_type##_t const *a_matrix, void const *b_packed_buffer, nk_##result_value_type##_t *c_matrix, \
1284
+ nk_size_t row_count, nk_size_t column_count, nk_size_t depth, nk_size_t a_stride_in_bytes, \
1285
+ nk_size_t c_stride_in_bytes) { \
1286
+ nk_cross_packed_buffer_header_t const *header = (nk_cross_packed_buffer_header_t const *)b_packed_buffer; \
1287
+ nk_size_t const depth_padded = header->depth_padded_values; \
1288
+ nk_size_t const row_block_size = 128; \
1289
+ nk_size_t const column_block_size = 2048; \
1290
+ nk_size_t const register_row_count = 4; \
1291
+ nk_size_t const register_column_count = 4; \
1292
+ nk_unused_(register_column_count); \
1293
+ if (column_count % 8 == 0 && column_count >= row_count * 2 && depth % depth_simd_dimensions == 0) { \
1294
+ nk_##api_name##_packed_##input_type_name##_##isa_suffix##_1x8_aligned_( \
1295
+ a_matrix, b_packed_buffer, c_matrix, row_count, column_count, depth, a_stride_in_bytes, \
1296
+ c_stride_in_bytes); \
1297
+ return; \
1298
+ } \
1299
+ if (row_count % 4 == 0 && column_count % 4 == 0 && depth % depth_simd_dimensions == 0) { \
1300
+ nk_##api_name##_packed_##input_type_name##_##isa_suffix##_aligned_(a_matrix, b_packed_buffer, c_matrix, \
1301
+ row_count, column_count, depth, \
1302
+ a_stride_in_bytes, c_stride_in_bytes); \
1303
+ return; \
1304
+ } \
1305
+ /* Generic fallback with partial loads and compensated finalize */ \
1306
+ nk_##packed_value_type##_t const *packed_data = \
1307
+ (nk_##packed_value_type##_t const *)((char const *)b_packed_buffer + \
1308
+ sizeof(nk_cross_packed_buffer_header_t)); \
1309
+ nk_size_t const total_packed_values = column_count * depth_padded; \
1310
+ nk_##norm_value_type##_t const *b_norms = (nk_##norm_value_type##_t const *)(packed_data + \
1311
+ total_packed_values); \
1312
+ nk_##sum_value_type##_t const *b_sums = (nk_##sum_value_type##_t const *)(b_norms + column_count); \
1313
+ nk_unused_(b_norms); \
1314
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
1315
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
1316
+ nk_size_t const depth_in_values = nk_size_divide_round_up_(depth, dimensions_per_value); \
1317
+ nk_size_t const remainder_depth = depth_in_values - aligned_depth; \
1318
+ nk_size_t const remainder_dimensions = depth - depth_dimensions_aligned; \
1319
+ nk_size_t const depth_step_values = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
1320
+ for (nk_size_t row_index = 0; row_index < row_count; ++row_index) { \
1321
+ nk_##result_value_type##_t *c_row = (nk_##result_value_type##_t *)((char *)c_matrix + \
1322
+ row_index * c_stride_in_bytes); \
1323
+ for (nk_size_t ci = 0; ci < column_count; ++ci) c_row[ci] = 0; \
1324
+ } \
1325
+ for (nk_size_t cb = 0; cb < column_count; cb += column_block_size) { \
1326
+ nk_size_t ce = cb + column_block_size; \
1327
+ if (ce > column_count) ce = column_count; \
1328
+ for (nk_size_t rb = 0; rb < row_count; rb += row_block_size) { \
1329
+ nk_size_t re = rb + row_block_size; \
1330
+ if (re > row_count) re = row_count; \
1331
+ for (nk_size_t tc = cb; tc < ce; tc += register_column_count) { \
1332
+ nk_size_t tile_col_count = register_column_count; \
1333
+ if (tc + tile_col_count > ce) tile_col_count = ce - tc; \
1334
+ nk_##packed_value_type##_t const *bdp0 = packed_data + (tc + 0) * depth_padded; \
1335
+ nk_##packed_value_type##_t const *bdp1 = (tile_col_count > 1) \
1336
+ ? packed_data + (tc + 1) * depth_padded \
1337
+ : bdp0; \
1338
+ nk_##packed_value_type##_t const *bdp2 = (tile_col_count > 2) \
1339
+ ? packed_data + (tc + 2) * depth_padded \
1340
+ : bdp0; \
1341
+ nk_##packed_value_type##_t const *bdp3 = (tile_col_count > 3) \
1342
+ ? packed_data + (tc + 3) * depth_padded \
1343
+ : bdp0; \
1344
+ result_vec_type b_sum_vec; \
1345
+ partial_load_sum_fn(b_sums + tc, &b_sum_vec, tile_col_count); \
1346
+ for (nk_size_t tr = rb; tr < re; tr += register_row_count) { \
1347
+ nk_size_t tile_row_count = register_row_count; \
1348
+ if (tr + tile_row_count > re) tile_row_count = re - tr; \
1349
+ state_type acc[4][4]; \
1350
+ for (nk_size_t rr = 0; rr < tile_row_count; ++rr) { \
1351
+ init_accumulator_fn(&acc[rr][0]); \
1352
+ init_accumulator_fn(&acc[rr][1]); \
1353
+ init_accumulator_fn(&acc[rr][2]); \
1354
+ init_accumulator_fn(&acc[rr][3]); \
1355
+ } \
1356
+ nk_##input_value_type##_t const *arp0 = \
1357
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1358
+ (tr + 0) * a_stride_in_bytes); \
1359
+ nk_##input_value_type##_t const *arp1 = \
1360
+ (tile_row_count > 1) ? (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1361
+ (tr + 1) * a_stride_in_bytes) \
1362
+ : arp0; \
1363
+ nk_##input_value_type##_t const *arp2 = \
1364
+ (tile_row_count > 2) ? (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1365
+ (tr + 2) * a_stride_in_bytes) \
1366
+ : arp0; \
1367
+ nk_##input_value_type##_t const *arp3 = \
1368
+ (tile_row_count > 3) ? (nk_##input_value_type##_t const *)((char const *)a_matrix + \
1369
+ (tr + 3) * a_stride_in_bytes) \
1370
+ : arp0; \
1371
+ nk_##sum_value_type##_t a_sums[4]; \
1372
+ a_sums[0] = compute_a_sum_fn(arp0, depth); \
1373
+ a_sums[1] = (tile_row_count > 1) ? compute_a_sum_fn(arp1, depth) : 0; \
1374
+ a_sums[2] = (tile_row_count > 2) ? compute_a_sum_fn(arp2, depth) : 0; \
1375
+ a_sums[3] = (tile_row_count > 3) ? compute_a_sum_fn(arp3, depth) : 0; \
1376
+ vec_type av0, av1, av2, av3, bv0, bv1, bv2, bv3; \
1377
+ for (nk_size_t k = 0; k < aligned_depth; k += depth_step_values) { \
1378
+ load_a_vec_fn(arp0 + k, &av0); \
1379
+ load_a_vec_fn(arp1 + k, &av1); \
1380
+ load_a_vec_fn(arp2 + k, &av2); \
1381
+ load_a_vec_fn(arp3 + k, &av3); \
1382
+ load_b_vec_fn(bdp0 + k, &bv0); \
1383
+ load_b_vec_fn(bdp1 + k, &bv1); \
1384
+ load_b_vec_fn(bdp2 + k, &bv2); \
1385
+ load_b_vec_fn(bdp3 + k, &bv3); \
1386
+ inner_product_fn(&acc[0][0], av0, bv0, k * dimensions_per_value, depth_simd_dimensions); \
1387
+ inner_product_fn(&acc[0][1], av0, bv1, k * dimensions_per_value, depth_simd_dimensions); \
1388
+ inner_product_fn(&acc[0][2], av0, bv2, k * dimensions_per_value, depth_simd_dimensions); \
1389
+ inner_product_fn(&acc[0][3], av0, bv3, k * dimensions_per_value, depth_simd_dimensions); \
1390
+ inner_product_fn(&acc[1][0], av1, bv0, k * dimensions_per_value, depth_simd_dimensions); \
1391
+ inner_product_fn(&acc[1][1], av1, bv1, k * dimensions_per_value, depth_simd_dimensions); \
1392
+ inner_product_fn(&acc[1][2], av1, bv2, k * dimensions_per_value, depth_simd_dimensions); \
1393
+ inner_product_fn(&acc[1][3], av1, bv3, k * dimensions_per_value, depth_simd_dimensions); \
1394
+ inner_product_fn(&acc[2][0], av2, bv0, k * dimensions_per_value, depth_simd_dimensions); \
1395
+ inner_product_fn(&acc[2][1], av2, bv1, k * dimensions_per_value, depth_simd_dimensions); \
1396
+ inner_product_fn(&acc[2][2], av2, bv2, k * dimensions_per_value, depth_simd_dimensions); \
1397
+ inner_product_fn(&acc[2][3], av2, bv3, k * dimensions_per_value, depth_simd_dimensions); \
1398
+ inner_product_fn(&acc[3][0], av3, bv0, k * dimensions_per_value, depth_simd_dimensions); \
1399
+ inner_product_fn(&acc[3][1], av3, bv1, k * dimensions_per_value, depth_simd_dimensions); \
1400
+ inner_product_fn(&acc[3][2], av3, bv2, k * dimensions_per_value, depth_simd_dimensions); \
1401
+ inner_product_fn(&acc[3][3], av3, bv3, k * dimensions_per_value, depth_simd_dimensions); \
1402
+ } \
1403
+ if (remainder_depth > 0) { \
1404
+ partial_load_a_vec_fn(arp0 + aligned_depth, &av0, remainder_dimensions); \
1405
+ partial_load_a_vec_fn(arp1 + aligned_depth, &av1, remainder_dimensions); \
1406
+ partial_load_a_vec_fn(arp2 + aligned_depth, &av2, remainder_dimensions); \
1407
+ partial_load_a_vec_fn(arp3 + aligned_depth, &av3, remainder_dimensions); \
1408
+ partial_load_b_vec_fn(bdp0 + aligned_depth, &bv0, remainder_dimensions); \
1409
+ partial_load_b_vec_fn(bdp1 + aligned_depth, &bv1, remainder_dimensions); \
1410
+ partial_load_b_vec_fn(bdp2 + aligned_depth, &bv2, remainder_dimensions); \
1411
+ partial_load_b_vec_fn(bdp3 + aligned_depth, &bv3, remainder_dimensions); \
1412
+ inner_product_fn(&acc[0][0], av0, bv0, aligned_depth * dimensions_per_value, \
1413
+ remainder_dimensions); \
1414
+ inner_product_fn(&acc[0][1], av0, bv1, aligned_depth * dimensions_per_value, \
1415
+ remainder_dimensions); \
1416
+ inner_product_fn(&acc[0][2], av0, bv2, aligned_depth * dimensions_per_value, \
1417
+ remainder_dimensions); \
1418
+ inner_product_fn(&acc[0][3], av0, bv3, aligned_depth * dimensions_per_value, \
1419
+ remainder_dimensions); \
1420
+ inner_product_fn(&acc[1][0], av1, bv0, aligned_depth * dimensions_per_value, \
1421
+ remainder_dimensions); \
1422
+ inner_product_fn(&acc[1][1], av1, bv1, aligned_depth * dimensions_per_value, \
1423
+ remainder_dimensions); \
1424
+ inner_product_fn(&acc[1][2], av1, bv2, aligned_depth * dimensions_per_value, \
1425
+ remainder_dimensions); \
1426
+ inner_product_fn(&acc[1][3], av1, bv3, aligned_depth * dimensions_per_value, \
1427
+ remainder_dimensions); \
1428
+ inner_product_fn(&acc[2][0], av2, bv0, aligned_depth * dimensions_per_value, \
1429
+ remainder_dimensions); \
1430
+ inner_product_fn(&acc[2][1], av2, bv1, aligned_depth * dimensions_per_value, \
1431
+ remainder_dimensions); \
1432
+ inner_product_fn(&acc[2][2], av2, bv2, aligned_depth * dimensions_per_value, \
1433
+ remainder_dimensions); \
1434
+ inner_product_fn(&acc[2][3], av2, bv3, aligned_depth * dimensions_per_value, \
1435
+ remainder_dimensions); \
1436
+ inner_product_fn(&acc[3][0], av3, bv0, aligned_depth * dimensions_per_value, \
1437
+ remainder_dimensions); \
1438
+ inner_product_fn(&acc[3][1], av3, bv1, aligned_depth * dimensions_per_value, \
1439
+ remainder_dimensions); \
1440
+ inner_product_fn(&acc[3][2], av3, bv2, aligned_depth * dimensions_per_value, \
1441
+ remainder_dimensions); \
1442
+ inner_product_fn(&acc[3][3], av3, bv3, aligned_depth * dimensions_per_value, \
1443
+ remainder_dimensions); \
1444
+ } \
1445
+ for (nk_size_t rr = 0; rr < tile_row_count; ++rr) { \
1446
+ result_vec_type rv; \
1447
+ compensated_finalize_fn(&acc[rr][0], &acc[rr][1], &acc[rr][2], &acc[rr][3], depth, \
1448
+ a_sums[rr], b_sum_vec, &rv); \
1449
+ nk_##result_value_type##_t *c_row = \
1450
+ (nk_##result_value_type##_t *)((char *)c_matrix + (tr + rr) * c_stride_in_bytes); \
1451
+ partial_store_fn(&rv, c_row + tc, tile_col_count); \
1452
+ } \
1453
+ } \
1454
+ } \
1455
+ } \
1456
+ } \
1457
+ }
1458
+
1459
+ /**
1460
+ * @brief Generates compensated symmetric Gram matrix: C = A × Aᵀ with inline correction.
1461
+ *
1462
+ * Like nk_define_cross_symmetric_ but the finalize function receives precomputed sums.
1463
+ * For symmetric computation, both row and column vectors come from the same matrix A,
1464
+ * so A sums serve as both row and column sums.
1465
+ *
1466
+ * The off-diagonal helper uses 4×4 tiling (matching nk_define_cross_symmetric_) with
1467
+ * progressive sum accumulation: SAD runs on port 5 alongside DPBUSD on ports 0+1 for
1468
+ * zero throughput overhead on Alder Lake and Ice Lake.
1469
+ */
1470
+ #define nk_define_cross_compensated_symmetric_( \
1471
+ api_name, input_type_name, isa_suffix, input_value_type, result_value_type, sum_value_type, norm_value_type, \
1472
+ vec_type, state_type, result_vec_type, init_accumulator_fn, load_vec_fn, partial_load_vec_fn, inner_product_fn, \
1473
+ compensated_finalize_fn, store_fn, partial_store_fn, load_sum_fn, partial_load_sum_fn, sum_state_type, \
1474
+ init_sum_fn, update_sum_fn, finalize_sum_fn, depth_simd_dimensions, dimensions_per_value) \
1475
+ NK_INTERNAL void nk_##api_name##_symmetric_diagonal_##input_type_name##_##isa_suffix##_( \
1476
+ nk_##input_value_type##_t const **vector_base_ptrs, nk_size_t i_macro, nk_size_t macro_size, \
1477
+ nk_size_t aligned_depth, nk_size_t remainder_depth, nk_size_t remainder_dimensions, \
1478
+ nk_size_t depth_step_values, nk_size_t dimensions_per_value_runtime, nk_##result_value_type##_t *result, \
1479
+ nk_size_t result_stride_values, nk_size_t finalizer_batch_size, nk_size_t depth) { \
1480
+ nk_unused_(finalizer_batch_size); \
1481
+ nk_unused_(dimensions_per_value_runtime); \
1482
+ /* Compute sums via stateful helpers — separate loop is fine since diagonal is ~1.6% of work */ \
1483
+ nk_size_t padded_depth_dimensions = aligned_depth * dimensions_per_value + \
1484
+ (remainder_depth > 0 ? depth_simd_dimensions : 0); \
1485
+ nk_##sum_value_type##_t precomputed_sums[32]; \
1486
+ for (nk_size_t s = 0; s < macro_size; s++) { \
1487
+ sum_state_type ss; \
1488
+ init_sum_fn(&ss); \
1489
+ for (nk_size_t di = 0; di < aligned_depth; di += depth_step_values) { \
1490
+ vec_type v; \
1491
+ load_vec_fn(vector_base_ptrs[s] + di, &v); \
1492
+ update_sum_fn(&ss, v); \
1493
+ } \
1494
+ if (remainder_depth > 0) { \
1495
+ vec_type v; \
1496
+ partial_load_vec_fn(vector_base_ptrs[s] + aligned_depth, &v, remainder_dimensions); \
1497
+ update_sum_fn(&ss, v); \
1498
+ } \
1499
+ precomputed_sums[s] = finalize_sum_fn(&ss, padded_depth_dimensions); \
1500
+ } \
1501
+ for (nk_size_t tile_row_start = 0; tile_row_start < macro_size; tile_row_start += 4) { \
1502
+ for (nk_size_t tile_col_start = tile_row_start; tile_col_start < macro_size; tile_col_start += 4) { \
1503
+ nk_size_t tile_rows = (tile_row_start + 4 <= macro_size) ? 4 : (macro_size - tile_row_start); \
1504
+ nk_size_t tile_cols = (tile_col_start + 4 <= macro_size) ? 4 : (macro_size - tile_col_start); \
1505
+ int is_diag = (tile_row_start == tile_col_start); \
1506
+ NK_ALIGN64 state_type accumulators[4][7]; \
1507
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
1508
+ nk_size_t init_start = is_diag ? row : 0; \
1509
+ nk_size_t init_end = is_diag ? (row + 4) : tile_cols; \
1510
+ for (nk_size_t col = init_start; col < init_end; col++) { \
1511
+ init_accumulator_fn(&accumulators[row][col]); \
1512
+ } \
1513
+ } \
1514
+ nk_##input_value_type##_t const *row_ptrs[4], *col_ptrs[4]; \
1515
+ row_ptrs[0] = vector_base_ptrs[tile_row_start + 0]; \
1516
+ row_ptrs[1] = (tile_rows > 1) ? vector_base_ptrs[tile_row_start + 1] : row_ptrs[0]; \
1517
+ row_ptrs[2] = (tile_rows > 2) ? vector_base_ptrs[tile_row_start + 2] : row_ptrs[0]; \
1518
+ row_ptrs[3] = (tile_rows > 3) ? vector_base_ptrs[tile_row_start + 3] : row_ptrs[0]; \
1519
+ if (is_diag) { \
1520
+ col_ptrs[0] = row_ptrs[0]; \
1521
+ col_ptrs[1] = row_ptrs[1]; \
1522
+ col_ptrs[2] = row_ptrs[2]; \
1523
+ col_ptrs[3] = row_ptrs[3]; \
1524
+ } \
1525
+ else { \
1526
+ col_ptrs[0] = vector_base_ptrs[tile_col_start + 0]; \
1527
+ col_ptrs[1] = (tile_cols > 1) ? vector_base_ptrs[tile_col_start + 1] : col_ptrs[0]; \
1528
+ col_ptrs[2] = (tile_cols > 2) ? vector_base_ptrs[tile_col_start + 2] : col_ptrs[0]; \
1529
+ col_ptrs[3] = (tile_cols > 3) ? vector_base_ptrs[tile_col_start + 3] : col_ptrs[0]; \
1530
+ } \
1531
+ vec_type row_vecs[4], col_vecs[4]; \
1532
+ for (nk_size_t di = 0; di < aligned_depth; di += depth_step_values) { \
1533
+ load_vec_fn(row_ptrs[0] + di, &row_vecs[0]); \
1534
+ load_vec_fn(row_ptrs[1] + di, &row_vecs[1]); \
1535
+ load_vec_fn(row_ptrs[2] + di, &row_vecs[2]); \
1536
+ load_vec_fn(row_ptrs[3] + di, &row_vecs[3]); \
1537
+ if (!is_diag) { \
1538
+ load_vec_fn(col_ptrs[0] + di, &col_vecs[0]); \
1539
+ load_vec_fn(col_ptrs[1] + di, &col_vecs[1]); \
1540
+ load_vec_fn(col_ptrs[2] + di, &col_vecs[2]); \
1541
+ load_vec_fn(col_ptrs[3] + di, &col_vecs[3]); \
1542
+ } \
1543
+ else { \
1544
+ col_vecs[0] = row_vecs[0]; \
1545
+ col_vecs[1] = row_vecs[1]; \
1546
+ col_vecs[2] = row_vecs[2]; \
1547
+ col_vecs[3] = row_vecs[3]; \
1548
+ } \
1549
+ if (tile_rows == 4 && tile_cols == 4 && is_diag) { \
1550
+ /* Upper triangle: 10 FMAs */ \
1551
+ inner_product_fn(&accumulators[0][0], row_vecs[0], col_vecs[0], di * dimensions_per_value, \
1552
+ depth_simd_dimensions); \
1553
+ inner_product_fn(&accumulators[0][1], row_vecs[0], col_vecs[1], di * dimensions_per_value, \
1554
+ depth_simd_dimensions); \
1555
+ inner_product_fn(&accumulators[0][2], row_vecs[0], col_vecs[2], di * dimensions_per_value, \
1556
+ depth_simd_dimensions); \
1557
+ inner_product_fn(&accumulators[0][3], row_vecs[0], col_vecs[3], di * dimensions_per_value, \
1558
+ depth_simd_dimensions); \
1559
+ inner_product_fn(&accumulators[1][1], row_vecs[1], col_vecs[1], di * dimensions_per_value, \
1560
+ depth_simd_dimensions); \
1561
+ inner_product_fn(&accumulators[1][2], row_vecs[1], col_vecs[2], di * dimensions_per_value, \
1562
+ depth_simd_dimensions); \
1563
+ inner_product_fn(&accumulators[1][3], row_vecs[1], col_vecs[3], di * dimensions_per_value, \
1564
+ depth_simd_dimensions); \
1565
+ inner_product_fn(&accumulators[2][2], row_vecs[2], col_vecs[2], di * dimensions_per_value, \
1566
+ depth_simd_dimensions); \
1567
+ inner_product_fn(&accumulators[2][3], row_vecs[2], col_vecs[3], di * dimensions_per_value, \
1568
+ depth_simd_dimensions); \
1569
+ inner_product_fn(&accumulators[3][3], row_vecs[3], col_vecs[3], di * dimensions_per_value, \
1570
+ depth_simd_dimensions); \
1571
+ } \
1572
+ else if (tile_rows == 4 && tile_cols == 4) { \
1573
+ /* Full 4×4 rectangle: 16 FMAs */ \
1574
+ inner_product_fn(&accumulators[0][0], row_vecs[0], col_vecs[0], di * dimensions_per_value, \
1575
+ depth_simd_dimensions); \
1576
+ inner_product_fn(&accumulators[0][1], row_vecs[0], col_vecs[1], di * dimensions_per_value, \
1577
+ depth_simd_dimensions); \
1578
+ inner_product_fn(&accumulators[0][2], row_vecs[0], col_vecs[2], di * dimensions_per_value, \
1579
+ depth_simd_dimensions); \
1580
+ inner_product_fn(&accumulators[0][3], row_vecs[0], col_vecs[3], di * dimensions_per_value, \
1581
+ depth_simd_dimensions); \
1582
+ inner_product_fn(&accumulators[1][0], row_vecs[1], col_vecs[0], di * dimensions_per_value, \
1583
+ depth_simd_dimensions); \
1584
+ inner_product_fn(&accumulators[1][1], row_vecs[1], col_vecs[1], di * dimensions_per_value, \
1585
+ depth_simd_dimensions); \
1586
+ inner_product_fn(&accumulators[1][2], row_vecs[1], col_vecs[2], di * dimensions_per_value, \
1587
+ depth_simd_dimensions); \
1588
+ inner_product_fn(&accumulators[1][3], row_vecs[1], col_vecs[3], di * dimensions_per_value, \
1589
+ depth_simd_dimensions); \
1590
+ inner_product_fn(&accumulators[2][0], row_vecs[2], col_vecs[0], di * dimensions_per_value, \
1591
+ depth_simd_dimensions); \
1592
+ inner_product_fn(&accumulators[2][1], row_vecs[2], col_vecs[1], di * dimensions_per_value, \
1593
+ depth_simd_dimensions); \
1594
+ inner_product_fn(&accumulators[2][2], row_vecs[2], col_vecs[2], di * dimensions_per_value, \
1595
+ depth_simd_dimensions); \
1596
+ inner_product_fn(&accumulators[2][3], row_vecs[2], col_vecs[3], di * dimensions_per_value, \
1597
+ depth_simd_dimensions); \
1598
+ inner_product_fn(&accumulators[3][0], row_vecs[3], col_vecs[0], di * dimensions_per_value, \
1599
+ depth_simd_dimensions); \
1600
+ inner_product_fn(&accumulators[3][1], row_vecs[3], col_vecs[1], di * dimensions_per_value, \
1601
+ depth_simd_dimensions); \
1602
+ inner_product_fn(&accumulators[3][2], row_vecs[3], col_vecs[2], di * dimensions_per_value, \
1603
+ depth_simd_dimensions); \
1604
+ inner_product_fn(&accumulators[3][3], row_vecs[3], col_vecs[3], di * dimensions_per_value, \
1605
+ depth_simd_dimensions); \
1606
+ } \
1607
+ else { \
1608
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
1609
+ nk_size_t col_start = is_diag ? row : 0; \
1610
+ nk_size_t col_end = is_diag ? (row < 4 ? 4 : tile_cols) : tile_cols; \
1611
+ for (nk_size_t col = col_start; col < col_end; col++) \
1612
+ inner_product_fn(&accumulators[row][col], row_vecs[row], col_vecs[col], \
1613
+ di * dimensions_per_value, depth_simd_dimensions); \
1614
+ } \
1615
+ } \
1616
+ } \
1617
+ if (remainder_depth > 0) { \
1618
+ partial_load_vec_fn(row_ptrs[0] + aligned_depth, &row_vecs[0], remainder_dimensions); \
1619
+ partial_load_vec_fn(row_ptrs[1] + aligned_depth, &row_vecs[1], remainder_dimensions); \
1620
+ partial_load_vec_fn(row_ptrs[2] + aligned_depth, &row_vecs[2], remainder_dimensions); \
1621
+ partial_load_vec_fn(row_ptrs[3] + aligned_depth, &row_vecs[3], remainder_dimensions); \
1622
+ if (!is_diag) { \
1623
+ partial_load_vec_fn(col_ptrs[0] + aligned_depth, &col_vecs[0], remainder_dimensions); \
1624
+ partial_load_vec_fn(col_ptrs[1] + aligned_depth, &col_vecs[1], remainder_dimensions); \
1625
+ partial_load_vec_fn(col_ptrs[2] + aligned_depth, &col_vecs[2], remainder_dimensions); \
1626
+ partial_load_vec_fn(col_ptrs[3] + aligned_depth, &col_vecs[3], remainder_dimensions); \
1627
+ } \
1628
+ else { \
1629
+ col_vecs[0] = row_vecs[0]; \
1630
+ col_vecs[1] = row_vecs[1]; \
1631
+ col_vecs[2] = row_vecs[2]; \
1632
+ col_vecs[3] = row_vecs[3]; \
1633
+ } \
1634
+ if (tile_rows == 4 && tile_cols == 4 && is_diag) { \
1635
+ inner_product_fn(&accumulators[0][0], row_vecs[0], col_vecs[0], \
1636
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1637
+ inner_product_fn(&accumulators[0][1], row_vecs[0], col_vecs[1], \
1638
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1639
+ inner_product_fn(&accumulators[0][2], row_vecs[0], col_vecs[2], \
1640
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1641
+ inner_product_fn(&accumulators[0][3], row_vecs[0], col_vecs[3], \
1642
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1643
+ inner_product_fn(&accumulators[1][1], row_vecs[1], col_vecs[1], \
1644
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1645
+ inner_product_fn(&accumulators[1][2], row_vecs[1], col_vecs[2], \
1646
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1647
+ inner_product_fn(&accumulators[1][3], row_vecs[1], col_vecs[3], \
1648
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1649
+ inner_product_fn(&accumulators[2][2], row_vecs[2], col_vecs[2], \
1650
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1651
+ inner_product_fn(&accumulators[2][3], row_vecs[2], col_vecs[3], \
1652
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1653
+ inner_product_fn(&accumulators[3][3], row_vecs[3], col_vecs[3], \
1654
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1655
+ } \
1656
+ else if (tile_rows == 4 && tile_cols == 4) { \
1657
+ inner_product_fn(&accumulators[0][0], row_vecs[0], col_vecs[0], \
1658
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1659
+ inner_product_fn(&accumulators[0][1], row_vecs[0], col_vecs[1], \
1660
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1661
+ inner_product_fn(&accumulators[0][2], row_vecs[0], col_vecs[2], \
1662
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1663
+ inner_product_fn(&accumulators[0][3], row_vecs[0], col_vecs[3], \
1664
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1665
+ inner_product_fn(&accumulators[1][0], row_vecs[1], col_vecs[0], \
1666
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1667
+ inner_product_fn(&accumulators[1][1], row_vecs[1], col_vecs[1], \
1668
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1669
+ inner_product_fn(&accumulators[1][2], row_vecs[1], col_vecs[2], \
1670
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1671
+ inner_product_fn(&accumulators[1][3], row_vecs[1], col_vecs[3], \
1672
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1673
+ inner_product_fn(&accumulators[2][0], row_vecs[2], col_vecs[0], \
1674
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1675
+ inner_product_fn(&accumulators[2][1], row_vecs[2], col_vecs[1], \
1676
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1677
+ inner_product_fn(&accumulators[2][2], row_vecs[2], col_vecs[2], \
1678
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1679
+ inner_product_fn(&accumulators[2][3], row_vecs[2], col_vecs[3], \
1680
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1681
+ inner_product_fn(&accumulators[3][0], row_vecs[3], col_vecs[0], \
1682
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1683
+ inner_product_fn(&accumulators[3][1], row_vecs[3], col_vecs[1], \
1684
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1685
+ inner_product_fn(&accumulators[3][2], row_vecs[3], col_vecs[2], \
1686
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1687
+ inner_product_fn(&accumulators[3][3], row_vecs[3], col_vecs[3], \
1688
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1689
+ } \
1690
+ else { \
1691
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
1692
+ nk_size_t col_start = is_diag ? row : 0; \
1693
+ nk_size_t col_end = is_diag ? (row < 4 ? 4 : tile_cols) : tile_cols; \
1694
+ for (nk_size_t col = col_start; col < col_end; col++) \
1695
+ inner_product_fn(&accumulators[row][col], row_vecs[row], col_vecs[col], \
1696
+ aligned_depth * dimensions_per_value, remainder_dimensions); \
1697
+ } \
1698
+ } \
1699
+ } \
1700
+ nk_##sum_value_type##_t row_sums[4] = {0}, col_sums_arr[4] = {0}; \
1701
+ for (nk_size_t r = 0; r < tile_rows; r++) row_sums[r] = precomputed_sums[tile_row_start + r]; \
1702
+ for (nk_size_t c = 0; c < tile_cols; c++) \
1703
+ col_sums_arr[c] = is_diag ? row_sums[c] : precomputed_sums[tile_col_start + c]; \
1704
+ /* Build column sums as SIMD vector — for diagonal tiles, shift per row */ \
1705
+ result_vec_type col_sum_vec; \
1706
+ if (!is_diag) partial_load_sum_fn(col_sums_arr, &col_sum_vec, tile_cols); \
1707
+ /* Finalize with compensation */ \
1708
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
1709
+ if (is_diag) { \
1710
+ nk_##sum_value_type##_t shifted[4] = {0}; \
1711
+ for (nk_size_t c = 0; c < 4 && (row + c) < tile_cols; c++) shifted[c] = col_sums_arr[row + c]; \
1712
+ partial_load_sum_fn(shifted, &col_sum_vec, 4); \
1713
+ } \
1714
+ result_vec_type rv; \
1715
+ compensated_finalize_fn( \
1716
+ &accumulators[row][is_diag ? row : 0], &accumulators[row][(is_diag ? row : 0) + 1], \
1717
+ &accumulators[row][(is_diag ? row : 0) + 2], &accumulators[row][(is_diag ? row : 0) + 3], \
1718
+ depth, row_sums[row], col_sum_vec, &rv); \
1719
+ nk_size_t global_row = i_macro + tile_row_start + row; \
1720
+ nk_size_t global_col_start = i_macro + tile_col_start + (is_diag ? row : 0); \
1721
+ nk_size_t store_count = is_diag ? (tile_cols - row) : tile_cols; \
1722
+ nk_##result_value_type##_t *dest = result + global_row * result_stride_values + global_col_start; \
1723
+ partial_store_fn(&rv, dest, store_count); \
1724
+ } \
1725
+ } \
1726
+ } \
1727
+ } \
1728
+ /* Off-diagonal helper: 4×4 tiling with inline sum accumulation (16 FMAs + up to 8 SADs per depth step) */ \
1729
+ NK_INTERNAL void nk_##api_name##_symmetric_offdiagonal_##input_type_name##_##isa_suffix##_( \
1730
+ nk_##input_value_type##_t const **row_ptrs_macro, nk_##input_value_type##_t const **col_ptrs_macro, \
1731
+ nk_size_t i_macro, nk_size_t j_macro, nk_size_t macro_i_size, nk_size_t macro_j_size, nk_size_t aligned_depth, \
1732
+ nk_size_t remainder_depth, nk_size_t remainder_dimensions, nk_size_t depth_step_values, \
1733
+ nk_size_t dimensions_per_value_runtime, nk_##result_value_type##_t *result, nk_size_t result_stride_values, \
1734
+ nk_size_t finalizer_batch_size, nk_size_t depth) { \
1735
+ nk_unused_(finalizer_batch_size); \
1736
+ nk_unused_(dimensions_per_value_runtime); \
1737
+ nk_size_t padded_depth_dimensions = aligned_depth * dimensions_per_value + \
1738
+ (remainder_depth > 0 ? depth_simd_dimensions : 0); \
1739
+ /* Sum caches for this macro-tile pair — computed once, reused across tiles */ \
1740
+ nk_##sum_value_type##_t row_sums[32], col_sums[32]; \
1741
+ for (nk_size_t tile_row_start = 0; tile_row_start < macro_i_size; tile_row_start += 4) { \
1742
+ for (nk_size_t tile_col_start = 0; tile_col_start < macro_j_size; tile_col_start += 4) { \
1743
+ nk_size_t tile_rows = (tile_row_start + 4 <= macro_i_size) ? 4 : (macro_i_size - tile_row_start); \
1744
+ nk_size_t tile_cols = (tile_col_start + 4 <= macro_j_size) ? 4 : (macro_j_size - tile_col_start); \
1745
+ /* Determine if this tile should compute sums — predictable branches */ \
1746
+ int compute_row_sums_flag = (tile_col_start == 0); \
1747
+ int compute_col_sums_flag = (tile_row_start == 0); \
1748
+ /* Initialize 4×4 dot accumulators */ \
1749
+ NK_ALIGN64 state_type accumulators[4][4]; \
1750
+ for (nk_size_t row = 0; row < tile_rows; row++) \
1751
+ for (nk_size_t col = 0; col < tile_cols; col++) init_accumulator_fn(&accumulators[row][col]); \
1752
+ /* Initialize sum accumulators (only when needed) */ \
1753
+ sum_state_type rsum[4], csum[4]; \
1754
+ if (compute_row_sums_flag) \
1755
+ for (nk_size_t r = 0; r < tile_rows; r++) init_sum_fn(&rsum[r]); \
1756
+ if (compute_col_sums_flag) \
1757
+ for (nk_size_t c = 0; c < tile_cols; c++) init_sum_fn(&csum[c]); \
1758
+ /* Setup pointers (hoist outside depth loop) */ \
1759
+ nk_##input_value_type##_t const *row_ptrs[4], *col_ptrs[4]; \
1760
+ row_ptrs[0] = row_ptrs_macro[tile_row_start + 0]; \
1761
+ row_ptrs[1] = (tile_rows > 1) ? row_ptrs_macro[tile_row_start + 1] : row_ptrs[0]; \
1762
+ row_ptrs[2] = (tile_rows > 2) ? row_ptrs_macro[tile_row_start + 2] : row_ptrs[0]; \
1763
+ row_ptrs[3] = (tile_rows > 3) ? row_ptrs_macro[tile_row_start + 3] : row_ptrs[0]; \
1764
+ col_ptrs[0] = col_ptrs_macro[tile_col_start + 0]; \
1765
+ col_ptrs[1] = (tile_cols > 1) ? col_ptrs_macro[tile_col_start + 1] : col_ptrs[0]; \
1766
+ col_ptrs[2] = (tile_cols > 2) ? col_ptrs_macro[tile_col_start + 2] : col_ptrs[0]; \
1767
+ col_ptrs[3] = (tile_cols > 3) ? col_ptrs_macro[tile_col_start + 3] : col_ptrs[0]; \
1768
+ /* Depth loop — innermost, 16 FMAs + up to 8 SADs per iteration */ \
1769
+ vec_type row_vecs[4], col_vecs[4]; \
1770
+ for (nk_size_t di = 0; di < aligned_depth; di += depth_step_values) { \
1771
+ load_vec_fn(row_ptrs[0] + di, &row_vecs[0]); \
1772
+ load_vec_fn(row_ptrs[1] + di, &row_vecs[1]); \
1773
+ load_vec_fn(row_ptrs[2] + di, &row_vecs[2]); \
1774
+ load_vec_fn(row_ptrs[3] + di, &row_vecs[3]); \
1775
+ load_vec_fn(col_ptrs[0] + di, &col_vecs[0]); \
1776
+ load_vec_fn(col_ptrs[1] + di, &col_vecs[1]); \
1777
+ load_vec_fn(col_ptrs[2] + di, &col_vecs[2]); \
1778
+ load_vec_fn(col_ptrs[3] + di, &col_vecs[3]); \
1779
+ nk_size_t vector_offset = di * dimensions_per_value; \
1780
+ if (tile_rows == 4 && tile_cols == 4) { \
1781
+ inner_product_fn(&accumulators[0][0], row_vecs[0], col_vecs[0], vector_offset, \
1782
+ depth_simd_dimensions); \
1783
+ inner_product_fn(&accumulators[0][1], row_vecs[0], col_vecs[1], vector_offset, \
1784
+ depth_simd_dimensions); \
1785
+ inner_product_fn(&accumulators[0][2], row_vecs[0], col_vecs[2], vector_offset, \
1786
+ depth_simd_dimensions); \
1787
+ inner_product_fn(&accumulators[0][3], row_vecs[0], col_vecs[3], vector_offset, \
1788
+ depth_simd_dimensions); \
1789
+ inner_product_fn(&accumulators[1][0], row_vecs[1], col_vecs[0], vector_offset, \
1790
+ depth_simd_dimensions); \
1791
+ inner_product_fn(&accumulators[1][1], row_vecs[1], col_vecs[1], vector_offset, \
1792
+ depth_simd_dimensions); \
1793
+ inner_product_fn(&accumulators[1][2], row_vecs[1], col_vecs[2], vector_offset, \
1794
+ depth_simd_dimensions); \
1795
+ inner_product_fn(&accumulators[1][3], row_vecs[1], col_vecs[3], vector_offset, \
1796
+ depth_simd_dimensions); \
1797
+ inner_product_fn(&accumulators[2][0], row_vecs[2], col_vecs[0], vector_offset, \
1798
+ depth_simd_dimensions); \
1799
+ inner_product_fn(&accumulators[2][1], row_vecs[2], col_vecs[1], vector_offset, \
1800
+ depth_simd_dimensions); \
1801
+ inner_product_fn(&accumulators[2][2], row_vecs[2], col_vecs[2], vector_offset, \
1802
+ depth_simd_dimensions); \
1803
+ inner_product_fn(&accumulators[2][3], row_vecs[2], col_vecs[3], vector_offset, \
1804
+ depth_simd_dimensions); \
1805
+ inner_product_fn(&accumulators[3][0], row_vecs[3], col_vecs[0], vector_offset, \
1806
+ depth_simd_dimensions); \
1807
+ inner_product_fn(&accumulators[3][1], row_vecs[3], col_vecs[1], vector_offset, \
1808
+ depth_simd_dimensions); \
1809
+ inner_product_fn(&accumulators[3][2], row_vecs[3], col_vecs[2], vector_offset, \
1810
+ depth_simd_dimensions); \
1811
+ inner_product_fn(&accumulators[3][3], row_vecs[3], col_vecs[3], vector_offset, \
1812
+ depth_simd_dimensions); \
1813
+ } \
1814
+ else { \
1815
+ for (nk_size_t row = 0; row < tile_rows; row++) \
1816
+ for (nk_size_t col = 0; col < tile_cols; col++) \
1817
+ inner_product_fn(&accumulators[row][col], row_vecs[row], col_vecs[col], vector_offset, \
1818
+ depth_simd_dimensions); \
1819
+ } \
1820
+ /* Progressive sum accumulation (SADs on port 5, parallel with DPBUSD on ports 0+1) */ \
1821
+ if (compute_row_sums_flag) { \
1822
+ update_sum_fn(&rsum[0], row_vecs[0]); \
1823
+ if (tile_rows > 1) update_sum_fn(&rsum[1], row_vecs[1]); \
1824
+ if (tile_rows > 2) update_sum_fn(&rsum[2], row_vecs[2]); \
1825
+ if (tile_rows > 3) update_sum_fn(&rsum[3], row_vecs[3]); \
1826
+ } \
1827
+ if (compute_col_sums_flag) { \
1828
+ update_sum_fn(&csum[0], col_vecs[0]); \
1829
+ if (tile_cols > 1) update_sum_fn(&csum[1], col_vecs[1]); \
1830
+ if (tile_cols > 2) update_sum_fn(&csum[2], col_vecs[2]); \
1831
+ if (tile_cols > 3) update_sum_fn(&csum[3], col_vecs[3]); \
1832
+ } \
1833
+ } \
1834
+ /* Handle remainder depth */ \
1835
+ if (remainder_depth > 0) { \
1836
+ partial_load_vec_fn(row_ptrs[0] + aligned_depth, &row_vecs[0], remainder_dimensions); \
1837
+ partial_load_vec_fn(row_ptrs[1] + aligned_depth, &row_vecs[1], remainder_dimensions); \
1838
+ partial_load_vec_fn(row_ptrs[2] + aligned_depth, &row_vecs[2], remainder_dimensions); \
1839
+ partial_load_vec_fn(row_ptrs[3] + aligned_depth, &row_vecs[3], remainder_dimensions); \
1840
+ partial_load_vec_fn(col_ptrs[0] + aligned_depth, &col_vecs[0], remainder_dimensions); \
1841
+ partial_load_vec_fn(col_ptrs[1] + aligned_depth, &col_vecs[1], remainder_dimensions); \
1842
+ partial_load_vec_fn(col_ptrs[2] + aligned_depth, &col_vecs[2], remainder_dimensions); \
1843
+ partial_load_vec_fn(col_ptrs[3] + aligned_depth, &col_vecs[3], remainder_dimensions); \
1844
+ nk_size_t vector_offset = aligned_depth * dimensions_per_value; \
1845
+ for (nk_size_t row = 0; row < tile_rows; row++) \
1846
+ for (nk_size_t col = 0; col < tile_cols; col++) \
1847
+ inner_product_fn(&accumulators[row][col], row_vecs[row], col_vecs[col], vector_offset, \
1848
+ remainder_dimensions); \
1849
+ if (compute_row_sums_flag) { \
1850
+ update_sum_fn(&rsum[0], row_vecs[0]); \
1851
+ if (tile_rows > 1) update_sum_fn(&rsum[1], row_vecs[1]); \
1852
+ if (tile_rows > 2) update_sum_fn(&rsum[2], row_vecs[2]); \
1853
+ if (tile_rows > 3) update_sum_fn(&rsum[3], row_vecs[3]); \
1854
+ } \
1855
+ if (compute_col_sums_flag) { \
1856
+ update_sum_fn(&csum[0], col_vecs[0]); \
1857
+ if (tile_cols > 1) update_sum_fn(&csum[1], col_vecs[1]); \
1858
+ if (tile_cols > 2) update_sum_fn(&csum[2], col_vecs[2]); \
1859
+ if (tile_cols > 3) update_sum_fn(&csum[3], col_vecs[3]); \
1860
+ } \
1861
+ } \
1862
+ /* Finalize and cache sums */ \
1863
+ if (compute_row_sums_flag) \
1864
+ for (nk_size_t r = 0; r < tile_rows; r++) \
1865
+ row_sums[tile_row_start + r] = finalize_sum_fn(&rsum[r], padded_depth_dimensions); \
1866
+ if (compute_col_sums_flag) \
1867
+ for (nk_size_t c = 0; c < tile_cols; c++) \
1868
+ col_sums[tile_col_start + c] = finalize_sum_fn(&csum[c], padded_depth_dimensions); \
1869
+ /* Build col_sum SIMD vector once (constant across rows) */ \
1870
+ nk_##sum_value_type##_t cs_arr[4] = {0}; \
1871
+ for (nk_size_t c = 0; c < tile_cols; c++) cs_arr[c] = col_sums[tile_col_start + c]; \
1872
+ result_vec_type cs_vec; \
1873
+ partial_load_sum_fn(cs_arr, &cs_vec, tile_cols); \
1874
+ /* Compensated finalize + store */ \
1875
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
1876
+ result_vec_type rv; \
1877
+ compensated_finalize_fn(&accumulators[row][0], &accumulators[row][1], &accumulators[row][2], \
1878
+ &accumulators[row][3], depth, row_sums[tile_row_start + row], cs_vec, \
1879
+ &rv); \
1880
+ nk_##result_value_type##_t *dest = result + \
1881
+ (i_macro + tile_row_start + row) * result_stride_values + \
1882
+ (j_macro + tile_col_start); \
1883
+ partial_store_fn(&rv, dest, tile_cols); \
1884
+ } \
1885
+ } \
1886
+ } \
1887
+ } \
1888
+ NK_PUBLIC void nk_##api_name##_symmetric_##input_type_name##_##isa_suffix( \
1889
+ nk_##input_value_type##_t const *vectors, nk_size_t vectors_count, nk_size_t depth, nk_size_t stride_in_bytes, \
1890
+ nk_##result_value_type##_t *result, nk_size_t result_stride_in_bytes, nk_size_t row_start, \
1891
+ nk_size_t row_count) { \
1892
+ nk_size_t const macro_tile_size = 32; \
1893
+ nk_size_t const row_block_size = 128; /* L2 cache blocking */ \
1894
+ nk_size_t const column_block_size = 2048; /* L3 cache blocking */ \
1895
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
1896
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
1897
+ nk_size_t const depth_in_values = nk_size_divide_round_up_(depth, dimensions_per_value); \
1898
+ nk_size_t const remainder_depth = depth_in_values - aligned_depth; \
1899
+ nk_size_t const remainder_dimensions = depth - depth_dimensions_aligned; \
1900
+ nk_size_t const depth_step = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
1901
+ nk_size_t const result_stride_values = result_stride_in_bytes / sizeof(nk_##result_value_type##_t); \
1902
+ nk_size_t const row_end = (row_start + row_count < vectors_count) ? (row_start + row_count) : vectors_count; \
1903
+ \
1904
+ /* Process upper triangle with L3/L2/L1 blocking (column blocks → row blocks → 32×32 macro-tiles) */ \
1905
+ for (nk_size_t j_block = 0; j_block < vectors_count; j_block += column_block_size) { \
1906
+ nk_size_t j_block_end = (j_block + column_block_size < vectors_count) ? j_block + column_block_size \
1907
+ : vectors_count; \
1908
+ \
1909
+ for (nk_size_t i_block = row_start; i_block < row_end; i_block += row_block_size) { \
1910
+ nk_size_t i_block_end = (i_block + row_block_size < row_end) ? i_block + row_block_size : row_end; \
1911
+ \
1912
+ /* Skip blocks entirely below diagonal. Blocks fully above the diagonal are still part of the upper \
1913
+ * triangle and must be computed. */ \
1914
+ if (i_block >= j_block_end) continue; \
1915
+ \
1916
+ for (nk_size_t i_macro = i_block; i_macro < i_block_end; i_macro += macro_tile_size) { \
1917
+ /* Upper triangle: j_macro starts at max(i_macro, j_block) */ \
1918
+ nk_size_t j_start = (i_macro > j_block) ? i_macro : j_block; \
1919
+ for (nk_size_t j_macro = j_start; j_macro < j_block_end; j_macro += macro_tile_size) { \
1920
+ nk_size_t macro_i_size = (i_macro + macro_tile_size <= i_block_end) ? macro_tile_size \
1921
+ : (i_block_end - i_macro); \
1922
+ nk_size_t macro_j_size = (j_macro + macro_tile_size <= j_block_end) ? macro_tile_size \
1923
+ : (j_block_end - j_macro); \
1924
+ \
1925
+ /* Build pointer arrays */ \
1926
+ nk_##input_value_type##_t const *vec_ptrs_i[32]; \
1927
+ nk_##input_value_type##_t const *vec_ptrs_j[32]; \
1928
+ for (nk_size_t k = 0; k < macro_i_size; k++) \
1929
+ vec_ptrs_i[k] = (nk_##input_value_type##_t const *)((char const *)vectors + \
1930
+ (i_macro + k) * stride_in_bytes); \
1931
+ for (nk_size_t k = macro_i_size; k < 32; k++) vec_ptrs_i[k] = vec_ptrs_i[0]; \
1932
+ \
1933
+ if (i_macro == j_macro && macro_i_size == macro_j_size) { \
1934
+ /* Diagonal macro-tile */ \
1935
+ nk_##api_name##_symmetric_diagonal_##input_type_name##_##isa_suffix##_( \
1936
+ vec_ptrs_i, i_macro, macro_i_size, aligned_depth, remainder_depth, \
1937
+ remainder_dimensions, depth_step, dimensions_per_value, result, result_stride_values, \
1938
+ 4, depth); \
1939
+ } \
1940
+ else { \
1941
+ /* Off-diagonal macro-tile */ \
1942
+ for (nk_size_t k = 0; k < macro_j_size; k++) \
1943
+ vec_ptrs_j[k] = (nk_##input_value_type##_t const *)((char const *)vectors + \
1944
+ (j_macro + k) * stride_in_bytes); \
1945
+ for (nk_size_t k = macro_j_size; k < 32; k++) vec_ptrs_j[k] = vec_ptrs_j[0]; \
1946
+ nk_##api_name##_symmetric_offdiagonal_##input_type_name##_##isa_suffix##_( \
1947
+ vec_ptrs_i, vec_ptrs_j, i_macro, j_macro, macro_i_size, macro_j_size, aligned_depth, \
1948
+ remainder_depth, remainder_dimensions, depth_step, dimensions_per_value, result, \
1949
+ result_stride_values, 4, depth); \
1950
+ } \
1951
+ } \
1952
+ } \
1953
+ } \
1954
+ } \
1955
+ }
1956
+
1957
+ /**
1958
+ * @brief Generates optimized symmetric Gram matrix computation: C = A × Aᵀ (upper triangle only).
1959
+ *
1960
+ * This macro creates a complete symmetric cross-product implementation with two specialized
1961
+ * internal helper functions (diagonal and off-diagonal) that are called by a public wrapper.
1962
+ * Symmetric computation exploits the property that C[i,j] = C[j,i], computing only the upper
1963
+ * triangle and avoiding redundant computation and storage.
1964
+ *
1965
+ * @par Mathematical Operation For each pair (i,j) where i ≤ j:
1966
+ * C[i,j] = operation(A[i,:], A[j,:])
1967
+ * where operation can be dot product, Hamming distance, Jaccard similarity, etc.
1968
+ *
1969
+ * @par Architecture - Three-Level Tiling Hierarchy
1970
+ *
1971
+ * 1. @b 32×32 @b macro-tiles (outermost): Divides the upper triangle into 32×32 blocks
1972
+ * - Rationale: Fits well in L1 cache (32 vectors × depth × value_size)
1973
+ * - Enables diagonal vs off-diagonal specialization
1974
+ * - Amortizes vector loads across all depth iterations
1975
+ * - Pre-loads and upcasts ALL 32 vectors ONCE per depth iteration (not per FMA)
1976
+ *
1977
+ * 2. @b 4×4 @b register @b tiles (middle): Within each macro-tile, process 4×4 sub-blocks
1978
+ * - Rationale: Maximizes register reuse (4 A vectors × 4 A vectors = 16 accumulators)
1979
+ * - Enables full FMA unrolling (16 FMAs for off-diagonal, 10 for diagonal)
1980
+ * - Balances register pressure with instruction-level parallelism
1981
+ *
1982
+ * 3. @b Depth @b loop (innermost): For each depth chunk, accumulate outer products
1983
+ * - Depth loop is INSIDE macro-tile, OUTSIDE register tiles
1984
+ * - Type conversion (e.g., bf16→f32) happens at macro-tile level (once per vector)
1985
+ *
1986
+ * @par Diagonal vs Off-Diagonal Optimization
1987
+ *
1988
+ * - @b Diagonal @b macro-tiles (i_macro == j_macro): Computes C[i:i+32, i:i+32]
1989
+ * - Loads 32 vectors ONCE (50% load reduction vs off-diagonal)
1990
+ * - Computes upper triangle only within the tile (10 FMAs per 4×4 block)
1991
+ * - Uses nk_##api_name##_symmetric_diagonal_##input_type_name##_##isa_suffix##_ helper
1992
+ *
1993
+ * - @b Off-diagonal @b macro-tiles (i_macro < j_macro): Computes C[i:i+32, j:j+32]
1994
+ * - Loads vec_i[32] + vec_j[32] (full 64 vectors for two sets)
1995
+ * - Computes full 32×32 block (16 FMAs per 4×4 block)
1996
+ * - Uses nk_##api_name##_symmetric_offdiagonal_##input_type_name##_##isa_suffix##_ helper
1997
+ *
1998
+ * @par When to Use Symmetric vs Packed Variant
1999
+ *
2000
+ * - Use symmetric (this macro) when: A is the SAME matrix for both sides (C = A × Aᵀ)
2001
+ * - Saves 50% computation and storage (upper triangle only)
2002
+ * - Automatic diagonal optimization (50% fewer loads on diagonal tiles)
2003
+ * - Ideal for: distance matrices, correlation matrices, Gram matrices
2004
+ *
2005
+ * - Use packed variant when: Computing C = A × Bᵀ where A ≠ B
2006
+ * - Full matrix computation (no symmetry to exploit)
2007
+ * - B can be pre-packed for cache efficiency
2008
+ *
2009
+ * @par Generated Functions
2010
+ *
2011
+ * This macro generates THREE functions:
2012
+ * 1. nk_##api_name##_symmetric_diagonal_##input_type_name##_##isa_suffix##_ (NK_INTERNAL)
2013
+ * 2. nk_##api_name##_symmetric_offdiagonal_##input_type_name##_##isa_suffix##_ (NK_INTERNAL)
2014
+ * 3. nk_##api_name##_symmetric_##input_type_name##_##isa_suffix (NK_PUBLIC wrapper)
2015
+ *
2016
+ * @param api_name Operation family (dots, hammings, jaccards) for codegen namespace
2017
+ * @param input_type_name Type identifier for codegen (f32, bf16, i8, u1, etc.)
2018
+ * @param isa_suffix ISA backend identifier (serial, haswell, neon, sve, icelake, etc.)
2019
+ * @param input_type C type of input matrix values (f32, bf16, i8, u1x8, etc.)
2020
+ * @param output_type C type of output matrix values (f32, u32, f64, etc.)
2021
+ * @param vec_type SIMD vector type for input vectors (e.g., __m256, nk_f32x8_t)
2022
+ * @param state_type Accumulator state type (often vec_type or wider, e.g., __m256 or __m512)
2023
+ * @param result_vec_type SIMD vector type for reduction results (e.g., __m128 for 4 f32 results)
2024
+ * @param init_accumulator_fn Initialize accumulator: void fn(state_type*)
2025
+ * @param load_vec_fn Full vector load: vec_type fn(input_type const*, nk_size_t offset)
2026
+ * @param partial_load_vec_fn Partial vector load for remainder
2027
+ * @param inner_product_fn Inner product accumulate
2028
+ * @param reduce_accumulators_fn Reduce 4 accumulators
2029
+ * @param partial_store_fn Partial store for results
2030
+ * @param depth_simd_dimensions SIMD vector width in logical dimensions (e.g., 8 for f32 on AVX2, 128 for u1 on serial)
2031
+ * @param dimensions_per_value Packing ratio: dimensions per storage value (1 for f32, 2 for i4x2, 8 for u1x8)
2032
+ *
2033
+ * @sa nk_define_cross_packed_ for asymmetric C = A × Bᵀ computation
2034
+ * @sa nk_define_cross_pack_size_ for calculating packed buffer size
2035
+ * @sa nk_define_cross_pack_ for packing B matrix
2036
+ * @sa include/numkong/set/serial.h for state type definitions
2037
+ * @sa include/numkong/cast/serial.h for load/store function implementations
2038
+ */
2039
+ #define nk_define_cross_symmetric_(api_name, input_type_name, isa_suffix, input_value_type, result_value_type, \
2040
+ vec_type, state_type, result_vec_type, init_accumulator_fn, load_vec_fn, \
2041
+ partial_load_vec_fn, inner_product_fn, reduce_accumulators_fn, store_fn, \
2042
+ partial_store_fn, depth_simd_dimensions, dimensions_per_value) \
2043
+ NK_INTERNAL void nk_##api_name##_symmetric_diagonal_##input_type_name##_##isa_suffix##_( \
2044
+ nk_##input_value_type##_t const **vector_base_ptrs, nk_size_t i_macro, nk_size_t macro_size, \
2045
+ nk_size_t aligned_depth, nk_size_t remainder_depth, nk_size_t remainder_dimensions, \
2046
+ nk_size_t depth_step_values, nk_size_t dimensions_per_value_runtime, nk_##result_value_type##_t *result, \
2047
+ nk_size_t result_stride_values, nk_size_t finalizer_batch_size, nk_size_t depth) { \
2048
+ \
2049
+ nk_unused_(dimensions_per_value_runtime); \
2050
+ nk_unused_(finalizer_batch_size); \
2051
+ /* Tile-first architecture: Process 32×32 macro-tile as 4×4 register tiles (depth innermost) */ \
2052
+ for (nk_size_t tile_row_start = 0; tile_row_start < macro_size; tile_row_start += 4) { \
2053
+ for (nk_size_t tile_column_start = tile_row_start; tile_column_start < macro_size; \
2054
+ tile_column_start += 4) { \
2055
+ \
2056
+ nk_size_t tile_rows = (tile_row_start + 4 <= macro_size) ? 4 : (macro_size - tile_row_start); \
2057
+ nk_size_t tile_columns = (tile_column_start + 4 <= macro_size) ? 4 : (macro_size - tile_column_start); \
2058
+ int is_diagonal_tile = (tile_row_start == tile_column_start); \
2059
+ \
2060
+ /* Initialize register-resident accumulators — padded to [4][7] so that the reduce call */ \
2061
+ /* (which always reads 4 consecutive entries starting at column_start) stays in bounds */ \
2062
+ NK_ALIGN64 state_type accumulators[4][7]; \
2063
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2064
+ nk_size_t init_start = is_diagonal_tile ? row : 0; \
2065
+ nk_size_t init_end = is_diagonal_tile ? (row + 4) : tile_columns; \
2066
+ for (nk_size_t column = init_start; column < init_end; column++) { \
2067
+ init_accumulator_fn(&accumulators[row][column]); \
2068
+ } \
2069
+ } \
2070
+ \
2071
+ /* Setup pointers (hoist outside depth loop) - always safe even for partial tiles */ \
2072
+ nk_##input_value_type##_t const *row_ptrs[4]; \
2073
+ nk_##input_value_type##_t const *column_ptrs[4]; \
2074
+ row_ptrs[0] = vector_base_ptrs[tile_row_start + 0]; \
2075
+ row_ptrs[1] = (tile_rows > 1) ? vector_base_ptrs[tile_row_start + 1] : row_ptrs[0]; \
2076
+ row_ptrs[2] = (tile_rows > 2) ? vector_base_ptrs[tile_row_start + 2] : row_ptrs[0]; \
2077
+ row_ptrs[3] = (tile_rows > 3) ? vector_base_ptrs[tile_row_start + 3] : row_ptrs[0]; \
2078
+ \
2079
+ if (is_diagonal_tile) { \
2080
+ column_ptrs[0] = row_ptrs[0]; \
2081
+ column_ptrs[1] = row_ptrs[1]; \
2082
+ column_ptrs[2] = row_ptrs[2]; \
2083
+ column_ptrs[3] = row_ptrs[3]; \
2084
+ } \
2085
+ else { \
2086
+ column_ptrs[0] = vector_base_ptrs[tile_column_start + 0]; \
2087
+ column_ptrs[1] = (tile_columns > 1) ? vector_base_ptrs[tile_column_start + 1] : column_ptrs[0]; \
2088
+ column_ptrs[2] = (tile_columns > 2) ? vector_base_ptrs[tile_column_start + 2] : column_ptrs[0]; \
2089
+ column_ptrs[3] = (tile_columns > 3) ? vector_base_ptrs[tile_column_start + 3] : column_ptrs[0]; \
2090
+ } \
2091
+ \
2092
+ /* Depth loop is now innermost - key optimization */ \
2093
+ vec_type row_vecs[4]; \
2094
+ vec_type column_vecs[4]; \
2095
+ \
2096
+ for (nk_size_t depth_offset = 0; depth_offset < aligned_depth; depth_offset += depth_step_values) { \
2097
+ /* Always load all 4 vectors - aliasing is cheaper than branches */ \
2098
+ load_vec_fn(row_ptrs[0] + depth_offset, &row_vecs[0]); \
2099
+ load_vec_fn(row_ptrs[1] + depth_offset, &row_vecs[1]); \
2100
+ load_vec_fn(row_ptrs[2] + depth_offset, &row_vecs[2]); \
2101
+ load_vec_fn(row_ptrs[3] + depth_offset, &row_vecs[3]); \
2102
+ \
2103
+ /* For diagonal tiles, column vectors alias row vectors (same memory) */ \
2104
+ load_vec_fn(column_ptrs[0] + depth_offset, &column_vecs[0]); \
2105
+ load_vec_fn(column_ptrs[1] + depth_offset, &column_vecs[1]); \
2106
+ load_vec_fn(column_ptrs[2] + depth_offset, &column_vecs[2]); \
2107
+ load_vec_fn(column_ptrs[3] + depth_offset, &column_vecs[3]); \
2108
+ \
2109
+ nk_size_t vector_offset = depth_offset * dimensions_per_value; \
2110
+ \
2111
+ /* Compute: always unroll for full 4×4, use loops only for partial tiles */ \
2112
+ if (tile_rows == 4 && tile_columns == 4) { \
2113
+ if (is_diagonal_tile) { \
2114
+ /* Full 4×4 diagonal tile - upper triangle only (10 FMAs) */ \
2115
+ inner_product_fn(&accumulators[0][0], row_vecs[0], column_vecs[0], vector_offset, \
2116
+ depth_simd_dimensions); \
2117
+ inner_product_fn(&accumulators[0][1], row_vecs[0], column_vecs[1], vector_offset, \
2118
+ depth_simd_dimensions); \
2119
+ inner_product_fn(&accumulators[0][2], row_vecs[0], column_vecs[2], vector_offset, \
2120
+ depth_simd_dimensions); \
2121
+ inner_product_fn(&accumulators[0][3], row_vecs[0], column_vecs[3], vector_offset, \
2122
+ depth_simd_dimensions); \
2123
+ inner_product_fn(&accumulators[1][1], row_vecs[1], column_vecs[1], vector_offset, \
2124
+ depth_simd_dimensions); \
2125
+ inner_product_fn(&accumulators[1][2], row_vecs[1], column_vecs[2], vector_offset, \
2126
+ depth_simd_dimensions); \
2127
+ inner_product_fn(&accumulators[1][3], row_vecs[1], column_vecs[3], vector_offset, \
2128
+ depth_simd_dimensions); \
2129
+ inner_product_fn(&accumulators[2][2], row_vecs[2], column_vecs[2], vector_offset, \
2130
+ depth_simd_dimensions); \
2131
+ inner_product_fn(&accumulators[2][3], row_vecs[2], column_vecs[3], vector_offset, \
2132
+ depth_simd_dimensions); \
2133
+ inner_product_fn(&accumulators[3][3], row_vecs[3], column_vecs[3], vector_offset, \
2134
+ depth_simd_dimensions); \
2135
+ } \
2136
+ else { \
2137
+ /* Full 4×4 off-diagonal tile (16 FMAs) */ \
2138
+ inner_product_fn(&accumulators[0][0], row_vecs[0], column_vecs[0], vector_offset, \
2139
+ depth_simd_dimensions); \
2140
+ inner_product_fn(&accumulators[0][1], row_vecs[0], column_vecs[1], vector_offset, \
2141
+ depth_simd_dimensions); \
2142
+ inner_product_fn(&accumulators[0][2], row_vecs[0], column_vecs[2], vector_offset, \
2143
+ depth_simd_dimensions); \
2144
+ inner_product_fn(&accumulators[0][3], row_vecs[0], column_vecs[3], vector_offset, \
2145
+ depth_simd_dimensions); \
2146
+ inner_product_fn(&accumulators[1][0], row_vecs[1], column_vecs[0], vector_offset, \
2147
+ depth_simd_dimensions); \
2148
+ inner_product_fn(&accumulators[1][1], row_vecs[1], column_vecs[1], vector_offset, \
2149
+ depth_simd_dimensions); \
2150
+ inner_product_fn(&accumulators[1][2], row_vecs[1], column_vecs[2], vector_offset, \
2151
+ depth_simd_dimensions); \
2152
+ inner_product_fn(&accumulators[1][3], row_vecs[1], column_vecs[3], vector_offset, \
2153
+ depth_simd_dimensions); \
2154
+ inner_product_fn(&accumulators[2][0], row_vecs[2], column_vecs[0], vector_offset, \
2155
+ depth_simd_dimensions); \
2156
+ inner_product_fn(&accumulators[2][1], row_vecs[2], column_vecs[1], vector_offset, \
2157
+ depth_simd_dimensions); \
2158
+ inner_product_fn(&accumulators[2][2], row_vecs[2], column_vecs[2], vector_offset, \
2159
+ depth_simd_dimensions); \
2160
+ inner_product_fn(&accumulators[2][3], row_vecs[2], column_vecs[3], vector_offset, \
2161
+ depth_simd_dimensions); \
2162
+ inner_product_fn(&accumulators[3][0], row_vecs[3], column_vecs[0], vector_offset, \
2163
+ depth_simd_dimensions); \
2164
+ inner_product_fn(&accumulators[3][1], row_vecs[3], column_vecs[1], vector_offset, \
2165
+ depth_simd_dimensions); \
2166
+ inner_product_fn(&accumulators[3][2], row_vecs[3], column_vecs[2], vector_offset, \
2167
+ depth_simd_dimensions); \
2168
+ inner_product_fn(&accumulators[3][3], row_vecs[3], column_vecs[3], vector_offset, \
2169
+ depth_simd_dimensions); \
2170
+ } \
2171
+ } \
2172
+ else { \
2173
+ /* Partial tile - use loops (rare edge case) */ \
2174
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2175
+ nk_size_t column_start = is_diagonal_tile ? row : 0; \
2176
+ for (nk_size_t column = column_start; column < tile_columns; column++) { \
2177
+ inner_product_fn(&accumulators[row][column], row_vecs[row], column_vecs[column], \
2178
+ vector_offset, depth_simd_dimensions); \
2179
+ } \
2180
+ } \
2181
+ } \
2182
+ } \
2183
+ \
2184
+ /* Handle remainder depth (happens once per tile, not in hot loop) */ \
2185
+ if (remainder_depth > 0) { \
2186
+ partial_load_vec_fn(row_ptrs[0] + aligned_depth, &row_vecs[0], remainder_dimensions); \
2187
+ partial_load_vec_fn(row_ptrs[1] + aligned_depth, &row_vecs[1], remainder_dimensions); \
2188
+ partial_load_vec_fn(row_ptrs[2] + aligned_depth, &row_vecs[2], remainder_dimensions); \
2189
+ partial_load_vec_fn(row_ptrs[3] + aligned_depth, &row_vecs[3], remainder_dimensions); \
2190
+ partial_load_vec_fn(column_ptrs[0] + aligned_depth, &column_vecs[0], remainder_dimensions); \
2191
+ partial_load_vec_fn(column_ptrs[1] + aligned_depth, &column_vecs[1], remainder_dimensions); \
2192
+ partial_load_vec_fn(column_ptrs[2] + aligned_depth, &column_vecs[2], remainder_dimensions); \
2193
+ partial_load_vec_fn(column_ptrs[3] + aligned_depth, &column_vecs[3], remainder_dimensions); \
2194
+ \
2195
+ nk_size_t vector_offset = aligned_depth * dimensions_per_value; \
2196
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2197
+ nk_size_t column_start = is_diagonal_tile ? row : 0; \
2198
+ for (nk_size_t column = column_start; column < tile_columns; column++) { \
2199
+ inner_product_fn(&accumulators[row][column], row_vecs[row], column_vecs[column], \
2200
+ vector_offset, remainder_dimensions); \
2201
+ } \
2202
+ } \
2203
+ } \
2204
+ \
2205
+ /* Direct finalization and store (no intermediate buffer) */ \
2206
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2207
+ nk_size_t column_start = is_diagonal_tile ? row : 0; \
2208
+ nk_size_t columns_remaining = tile_columns - column_start; \
2209
+ result_vec_type result_vec; \
2210
+ \
2211
+ /* Always reduce 4 accumulators (partial_store handles actual count) */ \
2212
+ reduce_accumulators_fn(&accumulators[row][column_start], &accumulators[row][column_start + 1], \
2213
+ &accumulators[row][column_start + 2], &accumulators[row][column_start + 3], \
2214
+ depth, &result_vec); \
2215
+ \
2216
+ nk_##result_value_type##_t *output_ptr = \
2217
+ &result[(i_macro + tile_row_start + row) * result_stride_values + \
2218
+ (i_macro + tile_column_start + column_start)]; \
2219
+ partial_store_fn(&result_vec, output_ptr, columns_remaining); \
2220
+ } \
2221
+ } \
2222
+ } \
2223
+ } \
2224
+ NK_INTERNAL void nk_##api_name##_symmetric_##input_type_name##_##isa_suffix##_offdiagonal_( \
2225
+ nk_##input_value_type##_t const **vector_base_ptrs_i, nk_##input_value_type##_t const **vector_base_ptrs_j, \
2226
+ nk_size_t i_macro, nk_size_t j_macro, nk_size_t macro_i_size, nk_size_t macro_j_size, nk_size_t aligned_depth, \
2227
+ nk_size_t remainder_depth, nk_size_t remainder_dimensions, nk_size_t depth_step_values, \
2228
+ nk_size_t dimensions_per_value_runtime, nk_##result_value_type##_t *result, nk_size_t result_stride_values, \
2229
+ nk_size_t finalizer_batch_size, nk_size_t depth) { \
2230
+ \
2231
+ nk_unused_(dimensions_per_value_runtime); \
2232
+ nk_unused_(finalizer_batch_size); \
2233
+ /* Tile-first architecture: Process 32×32 macro-tile as 4×4 register tiles (depth innermost) */ \
2234
+ for (nk_size_t tile_row_start = 0; tile_row_start < macro_i_size; tile_row_start += 4) { \
2235
+ for (nk_size_t tile_column_start = 0; tile_column_start < macro_j_size; tile_column_start += 4) { \
2236
+ \
2237
+ nk_size_t tile_rows = (tile_row_start + 4 <= macro_i_size) ? 4 : (macro_i_size - tile_row_start); \
2238
+ nk_size_t tile_columns = (tile_column_start + 4 <= macro_j_size) ? 4 \
2239
+ : (macro_j_size - tile_column_start); \
2240
+ \
2241
+ /* Initialize 4×4 register-resident accumulators (full rectangle for off-diagonal) */ \
2242
+ NK_ALIGN64 state_type accumulators[4][4]; \
2243
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2244
+ for (nk_size_t column = 0; column < tile_columns; column++) { \
2245
+ init_accumulator_fn(&accumulators[row][column]); \
2246
+ } \
2247
+ } \
2248
+ \
2249
+ /* Setup pointers (hoist outside depth loop) - always safe even for partial tiles */ \
2250
+ nk_##input_value_type##_t const *row_ptrs[4]; \
2251
+ nk_##input_value_type##_t const *column_ptrs[4]; \
2252
+ row_ptrs[0] = vector_base_ptrs_i[tile_row_start + 0]; \
2253
+ row_ptrs[1] = (tile_rows > 1) ? vector_base_ptrs_i[tile_row_start + 1] : row_ptrs[0]; \
2254
+ row_ptrs[2] = (tile_rows > 2) ? vector_base_ptrs_i[tile_row_start + 2] : row_ptrs[0]; \
2255
+ row_ptrs[3] = (tile_rows > 3) ? vector_base_ptrs_i[tile_row_start + 3] : row_ptrs[0]; \
2256
+ column_ptrs[0] = vector_base_ptrs_j[tile_column_start + 0]; \
2257
+ column_ptrs[1] = (tile_columns > 1) ? vector_base_ptrs_j[tile_column_start + 1] : column_ptrs[0]; \
2258
+ column_ptrs[2] = (tile_columns > 2) ? vector_base_ptrs_j[tile_column_start + 2] : column_ptrs[0]; \
2259
+ column_ptrs[3] = (tile_columns > 3) ? vector_base_ptrs_j[tile_column_start + 3] : column_ptrs[0]; \
2260
+ \
2261
+ /* Depth loop is now innermost - key optimization */ \
2262
+ vec_type row_vecs[4]; \
2263
+ vec_type column_vecs[4]; \
2264
+ \
2265
+ for (nk_size_t depth_offset = 0; depth_offset < aligned_depth; depth_offset += depth_step_values) { \
2266
+ /* Always load all 8 vectors - aliasing is cheaper than branches */ \
2267
+ load_vec_fn(row_ptrs[0] + depth_offset, &row_vecs[0]); \
2268
+ load_vec_fn(row_ptrs[1] + depth_offset, &row_vecs[1]); \
2269
+ load_vec_fn(row_ptrs[2] + depth_offset, &row_vecs[2]); \
2270
+ load_vec_fn(row_ptrs[3] + depth_offset, &row_vecs[3]); \
2271
+ load_vec_fn(column_ptrs[0] + depth_offset, &column_vecs[0]); \
2272
+ load_vec_fn(column_ptrs[1] + depth_offset, &column_vecs[1]); \
2273
+ load_vec_fn(column_ptrs[2] + depth_offset, &column_vecs[2]); \
2274
+ load_vec_fn(column_ptrs[3] + depth_offset, &column_vecs[3]); \
2275
+ \
2276
+ nk_size_t vector_offset = depth_offset * dimensions_per_value; \
2277
+ \
2278
+ /* Compute: always unroll for full 4×4, use loops only for partial tiles */ \
2279
+ if (tile_rows == 4 && tile_columns == 4) { \
2280
+ /* Full 4×4 off-diagonal tile (16 FMAs) */ \
2281
+ inner_product_fn(&accumulators[0][0], row_vecs[0], column_vecs[0], vector_offset, \
2282
+ depth_simd_dimensions); \
2283
+ inner_product_fn(&accumulators[0][1], row_vecs[0], column_vecs[1], vector_offset, \
2284
+ depth_simd_dimensions); \
2285
+ inner_product_fn(&accumulators[0][2], row_vecs[0], column_vecs[2], vector_offset, \
2286
+ depth_simd_dimensions); \
2287
+ inner_product_fn(&accumulators[0][3], row_vecs[0], column_vecs[3], vector_offset, \
2288
+ depth_simd_dimensions); \
2289
+ inner_product_fn(&accumulators[1][0], row_vecs[1], column_vecs[0], vector_offset, \
2290
+ depth_simd_dimensions); \
2291
+ inner_product_fn(&accumulators[1][1], row_vecs[1], column_vecs[1], vector_offset, \
2292
+ depth_simd_dimensions); \
2293
+ inner_product_fn(&accumulators[1][2], row_vecs[1], column_vecs[2], vector_offset, \
2294
+ depth_simd_dimensions); \
2295
+ inner_product_fn(&accumulators[1][3], row_vecs[1], column_vecs[3], vector_offset, \
2296
+ depth_simd_dimensions); \
2297
+ inner_product_fn(&accumulators[2][0], row_vecs[2], column_vecs[0], vector_offset, \
2298
+ depth_simd_dimensions); \
2299
+ inner_product_fn(&accumulators[2][1], row_vecs[2], column_vecs[1], vector_offset, \
2300
+ depth_simd_dimensions); \
2301
+ inner_product_fn(&accumulators[2][2], row_vecs[2], column_vecs[2], vector_offset, \
2302
+ depth_simd_dimensions); \
2303
+ inner_product_fn(&accumulators[2][3], row_vecs[2], column_vecs[3], vector_offset, \
2304
+ depth_simd_dimensions); \
2305
+ inner_product_fn(&accumulators[3][0], row_vecs[3], column_vecs[0], vector_offset, \
2306
+ depth_simd_dimensions); \
2307
+ inner_product_fn(&accumulators[3][1], row_vecs[3], column_vecs[1], vector_offset, \
2308
+ depth_simd_dimensions); \
2309
+ inner_product_fn(&accumulators[3][2], row_vecs[3], column_vecs[2], vector_offset, \
2310
+ depth_simd_dimensions); \
2311
+ inner_product_fn(&accumulators[3][3], row_vecs[3], column_vecs[3], vector_offset, \
2312
+ depth_simd_dimensions); \
2313
+ } \
2314
+ else { \
2315
+ /* Partial tile - use loops (rare edge case) */ \
2316
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2317
+ for (nk_size_t column = 0; column < tile_columns; column++) { \
2318
+ inner_product_fn(&accumulators[row][column], row_vecs[row], column_vecs[column], \
2319
+ vector_offset, depth_simd_dimensions); \
2320
+ } \
2321
+ } \
2322
+ } \
2323
+ } \
2324
+ \
2325
+ /* Handle remainder depth (happens once per tile, not in hot loop) */ \
2326
+ if (remainder_depth > 0) { \
2327
+ partial_load_vec_fn(row_ptrs[0] + aligned_depth, &row_vecs[0], remainder_dimensions); \
2328
+ partial_load_vec_fn(row_ptrs[1] + aligned_depth, &row_vecs[1], remainder_dimensions); \
2329
+ partial_load_vec_fn(row_ptrs[2] + aligned_depth, &row_vecs[2], remainder_dimensions); \
2330
+ partial_load_vec_fn(row_ptrs[3] + aligned_depth, &row_vecs[3], remainder_dimensions); \
2331
+ partial_load_vec_fn(column_ptrs[0] + aligned_depth, &column_vecs[0], remainder_dimensions); \
2332
+ partial_load_vec_fn(column_ptrs[1] + aligned_depth, &column_vecs[1], remainder_dimensions); \
2333
+ partial_load_vec_fn(column_ptrs[2] + aligned_depth, &column_vecs[2], remainder_dimensions); \
2334
+ partial_load_vec_fn(column_ptrs[3] + aligned_depth, &column_vecs[3], remainder_dimensions); \
2335
+ \
2336
+ nk_size_t vector_offset = aligned_depth * dimensions_per_value; \
2337
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2338
+ for (nk_size_t column = 0; column < tile_columns; column++) { \
2339
+ inner_product_fn(&accumulators[row][column], row_vecs[row], column_vecs[column], \
2340
+ vector_offset, remainder_dimensions); \
2341
+ } \
2342
+ } \
2343
+ } \
2344
+ \
2345
+ /* Direct finalization and store (no intermediate buffer) */ \
2346
+ for (nk_size_t row = 0; row < tile_rows; row++) { \
2347
+ result_vec_type result_vec; \
2348
+ \
2349
+ /* Always reduce 4 accumulators (partial_store handles actual count) */ \
2350
+ reduce_accumulators_fn(&accumulators[row][0], &accumulators[row][1], &accumulators[row][2], \
2351
+ &accumulators[row][3], depth, &result_vec); \
2352
+ \
2353
+ nk_##result_value_type##_t *output_ptr = \
2354
+ &result[(i_macro + tile_row_start + row) * result_stride_values + \
2355
+ (j_macro + tile_column_start)]; \
2356
+ partial_store_fn(&result_vec, output_ptr, tile_columns); \
2357
+ } \
2358
+ } \
2359
+ } \
2360
+ } \
2361
+ NK_PUBLIC void nk_##api_name##_symmetric_##input_type_name##_##isa_suffix( \
2362
+ nk_##input_value_type##_t const *vectors, nk_size_t vectors_count, nk_size_t depth, nk_size_t stride_in_bytes, \
2363
+ nk_##result_value_type##_t *result, nk_size_t result_stride_in_bytes, nk_size_t row_start, \
2364
+ nk_size_t row_count) { \
2365
+ nk_size_t const macro_tile_size = 32; \
2366
+ nk_size_t const finalizer_batch_size = 4; \
2367
+ nk_size_t const row_block_size = 128; /* L2 cache blocking */ \
2368
+ nk_size_t const column_block_size = 2048; /* L3 cache blocking */ \
2369
+ \
2370
+ /* Stride and depth calculations */ \
2371
+ nk_size_t const vectors_stride_values = stride_in_bytes / sizeof(nk_##input_value_type##_t); \
2372
+ nk_size_t const result_stride_values = result_stride_in_bytes / sizeof(nk_##result_value_type##_t); \
2373
+ nk_size_t const depth_dimensions_aligned = (depth / depth_simd_dimensions) * depth_simd_dimensions; \
2374
+ nk_size_t const aligned_depth = nk_size_divide_round_up_(depth_dimensions_aligned, dimensions_per_value); \
2375
+ nk_size_t const depth_in_values = nk_size_divide_round_up_(depth, dimensions_per_value); \
2376
+ nk_size_t const remainder_depth = depth_in_values - aligned_depth; \
2377
+ nk_size_t const remainder_dimensions = depth - depth_dimensions_aligned; \
2378
+ nk_size_t const depth_step_values = nk_size_divide_round_up_(depth_simd_dimensions, dimensions_per_value); \
2379
+ nk_size_t const row_end = (row_start + row_count < vectors_count) ? (row_start + row_count) : vectors_count; \
2380
+ \
2381
+ /* Process upper triangle with L3/L2/L1 blocking (column blocks → row blocks → 32×32 macro-tiles) */ \
2382
+ for (nk_size_t j_block = 0; j_block < vectors_count; j_block += column_block_size) { \
2383
+ nk_size_t j_block_end = (j_block + column_block_size < vectors_count) ? j_block + column_block_size \
2384
+ : vectors_count; \
2385
+ \
2386
+ for (nk_size_t i_block = row_start; i_block < row_end; i_block += row_block_size) { \
2387
+ nk_size_t i_block_end = (i_block + row_block_size < row_end) ? i_block + row_block_size : row_end; \
2388
+ \
2389
+ /* Skip blocks entirely below diagonal. Blocks fully above the diagonal are still part of the upper \
2390
+ * triangle and must be computed. */ \
2391
+ if (i_block >= j_block_end) continue; \
2392
+ \
2393
+ for (nk_size_t i_macro = i_block; i_macro < i_block_end; i_macro += macro_tile_size) { \
2394
+ /* Upper triangle: j_macro starts at max(i_macro, j_block) */ \
2395
+ nk_size_t j_start = (i_macro > j_block) ? i_macro : j_block; \
2396
+ for (nk_size_t j_macro = j_start; j_macro < j_block_end; j_macro += macro_tile_size) { \
2397
+ nk_size_t macro_i_size = (i_macro + macro_tile_size <= i_block_end) ? macro_tile_size \
2398
+ : (i_block_end - i_macro); \
2399
+ nk_size_t macro_j_size = (j_macro + macro_tile_size <= j_block_end) ? macro_tile_size \
2400
+ : (j_block_end - j_macro); \
2401
+ \
2402
+ /* Hoist pointer computation outside depth loop */ \
2403
+ nk_##input_value_type##_t const *vector_base_ptrs_i[32]; \
2404
+ nk_##input_value_type##_t const *vector_base_ptrs_j[32]; \
2405
+ for (nk_size_t i = 0; i < macro_i_size; i++) { \
2406
+ vector_base_ptrs_i[i] = vectors + (i_macro + i) * vectors_stride_values; \
2407
+ } \
2408
+ if (i_macro != j_macro || macro_i_size != macro_j_size) { \
2409
+ for (nk_size_t j = 0; j < macro_j_size; j++) { \
2410
+ vector_base_ptrs_j[j] = vectors + (j_macro + j) * vectors_stride_values; \
2411
+ } \
2412
+ } \
2413
+ \
2414
+ if (i_macro == j_macro && macro_i_size == macro_j_size) { \
2415
+ /* Diagonal macro-tile: symmetric, upper triangle only */ \
2416
+ nk_##api_name##_symmetric_diagonal_##input_type_name##_##isa_suffix##_( \
2417
+ vector_base_ptrs_i, i_macro, macro_i_size, aligned_depth, remainder_depth, \
2418
+ remainder_dimensions, depth_step_values, dimensions_per_value, result, \
2419
+ result_stride_values, finalizer_batch_size, depth); \
2420
+ } \
2421
+ else { \
2422
+ /* Off-diagonal macro-tile: full rectangle */ \
2423
+ nk_##api_name##_symmetric_##input_type_name##_##isa_suffix##_offdiagonal##_( \
2424
+ vector_base_ptrs_i, vector_base_ptrs_j, i_macro, j_macro, macro_i_size, macro_j_size, \
2425
+ aligned_depth, remainder_depth, remainder_dimensions, depth_step_values, \
2426
+ dimensions_per_value, result, result_stride_values, finalizer_batch_size, depth); \
2427
+ } \
2428
+ } \
2429
+ } \
2430
+ } \
2431
+ } \
2432
+ }
2433
+
2434
+ /* Optimize serial GEMM instantiations for size rather than speed.
2435
+ * These fallback kernels are only used when no SIMD backend is available, so aggressive inlining/unrolling from -O3
2436
+ * wastes over 1 MB of binary space with negligible performance benefit on the serial path.
2437
+ */
2438
+ #if defined(NDEBUG)
2439
+ #if defined(_MSC_VER)
2440
+ #pragma optimize("s", on)
2441
+ #elif defined(__clang__)
2442
+ #pragma clang attribute push(__attribute__((minsize)), apply_to = function)
2443
+ #elif defined(__GNUC__)
2444
+ #pragma GCC push_options
2445
+ #pragma GCC optimize("Os")
2446
+ #endif
2447
+ #endif
2448
+
2449
+ /* F64 GEMM: depth_simd_dimensions=2 (2 f64s = 16 bytes) */
2450
+ nk_define_cross_pack_size_(dots, f64, serial, f64, f64, /*norm_value_type=*/f64, /*depth_simd_dimensions=*/2,
2451
+ /*dimensions_per_value=*/1)
2452
+ nk_define_cross_pack_(dots, f64, serial, f64, f64, nk_b128_vec_t, nk_load_b128_serial_, nk_partial_load_b64x2_serial_,
2453
+ nk_store_b128_serial_, nk_partial_store_b64x2_serial_, /*simd_width=*/2, /*norm_value_type=*/f64,
2454
+ nk_dots_reduce_sumsq_f64_, /*depth_simd_dimensions=*/2, /*dimensions_per_value=*/1)
2455
+ nk_define_cross_symmetric_(dots, f64, serial, f64, f64, nk_b128_vec_t, nk_dot_f64x2_state_serial_t, nk_b256_vec_t,
2456
+ nk_dot_f64x2_init_serial, nk_load_b128_serial_, nk_partial_load_b64x2_serial_,
2457
+ nk_dot_f64x2_update_serial, nk_dot_f64x2_finalize_serial, nk_store_b256_serial_,
2458
+ nk_partial_store_b64x4_serial_,
2459
+ /*depth_simd_dimensions=*/2, /*dimensions_per_value=*/1)
2460
+ nk_define_cross_packed_(dots, f64, serial, f64, f64, f64, nk_b128_vec_t, nk_dot_f64x2_state_serial_t, nk_b256_vec_t,
2461
+ nk_dot_f64x2_init_serial, nk_load_b128_serial_, nk_partial_load_b64x2_serial_,
2462
+ nk_load_b128_serial_, nk_partial_load_b64x2_serial_, nk_dot_f64x2_update_serial,
2463
+ nk_dot_f64x2_finalize_serial, nk_store_b256_serial_, nk_partial_store_b64x4_serial_,
2464
+ /*depth_simd_dimensions=*/2, /*dimensions_per_value=*/1)
2465
+
2466
+ /* F32 GEMM: depth_simd_dimensions=4 (4 f32s = 16 bytes) */
2467
+ nk_define_cross_pack_size_(dots, f32, serial, f32, f32, /*norm_value_type=*/f64, /*depth_simd_dimensions=*/4,
2468
+ /*dimensions_per_value=*/1)
2469
+ nk_define_cross_pack_(dots, f32, serial, f32, f32, nk_b128_vec_t, nk_load_b128_serial_, nk_partial_load_b32x4_serial_,
2470
+ nk_store_b128_serial_, nk_partial_store_b32x4_serial_, /*simd_width=*/4, /*norm_value_type=*/f64,
2471
+ nk_dots_reduce_sumsq_f32_, /*depth_simd_dimensions=*/4, /*dimensions_per_value=*/1)
2472
+ nk_define_cross_symmetric_(dots, f32, serial, f32, f64, nk_b128_vec_t, nk_dot_f32x4_state_serial_t, nk_b256_vec_t,
2473
+ nk_dot_f32x4_init_serial, nk_load_b128_serial_, nk_partial_load_b32x4_serial_,
2474
+ nk_dot_f32x4_update_serial, nk_dot_f32x4_finalize_serial, nk_store_b256_serial_,
2475
+ nk_partial_store_b64x4_serial_,
2476
+ /*depth_simd_dimensions=*/4, /*dimensions_per_value=*/1)
2477
+ nk_define_cross_packed_(dots, f32, serial, f32, f32, f64, nk_b128_vec_t, nk_dot_f32x4_state_serial_t, nk_b256_vec_t,
2478
+ nk_dot_f32x4_init_serial, nk_load_b128_serial_, nk_partial_load_b32x4_serial_,
2479
+ nk_load_b128_serial_, nk_partial_load_b32x4_serial_, nk_dot_f32x4_update_serial,
2480
+ nk_dot_f32x4_finalize_serial, nk_store_b256_serial_, nk_partial_store_b64x4_serial_,
2481
+ /*depth_simd_dimensions=*/4, /*dimensions_per_value=*/1)
2482
+
2483
+ /* F16 packed GEMM: pre-upcast B to f32 and process 4 logical dimensions per 128-bit step. */
2484
+ nk_define_cross_pack_size_(dots, f16, serial, f16, f32, /*norm_value_type=*/f32, /*depth_simd_dimensions=*/4,
2485
+ /*dimensions_per_value=*/1)
2486
+ nk_define_cross_pack_(dots, f16, serial, f16, f32, nk_b128_vec_t, nk_load_f16x4_to_f32x4_serial_,
2487
+ nk_partial_load_f16x4_to_f32x4_serial_, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2488
+ /*simd_width=*/4, /*norm_value_type=*/f32, nk_dots_reduce_sumsq_f16_,
2489
+ /*depth_simd_dimensions=*/4, /*dimensions_per_value=*/1)
2490
+ nk_define_cross_symmetric_(dots, f16, serial, f16, f32, nk_b128_vec_t, nk_dot_f16x8_state_serial_t, nk_b128_vec_t,
2491
+ nk_dot_f16x8_init_serial, nk_load_b128_serial_, nk_partial_load_b16x8_serial_,
2492
+ nk_dot_f16x8_update_serial, nk_dot_f16x8_finalize_serial, nk_store_b128_serial_,
2493
+ nk_partial_store_b32x4_serial_,
2494
+ /*depth_simd_dimensions=*/8, /*dimensions_per_value=*/1)
2495
+ nk_define_cross_packed_(dots, f16, serial, f16, f32, f32, nk_b128_vec_t, nk_dot_through_f32x4_state_serial_t,
2496
+ nk_b128_vec_t, nk_dot_through_f32x4_init_serial, nk_load_f16x4_to_f32x4_serial_,
2497
+ nk_partial_load_f16x4_to_f32x4_serial_, nk_load_b128_serial_, nk_partial_load_b32x4_serial_,
2498
+ nk_dot_through_f32x4_update_serial, nk_dot_through_f32x4_finalize_serial, nk_store_b128_serial_,
2499
+ nk_partial_store_b32x4_serial_,
2500
+ /*depth_simd_dimensions=*/4, /*dimensions_per_value=*/1)
2501
+
2502
+ /* BF16 GEMM: depth_simd_dimensions=8 (8 bf16s = 16 bytes), F32 accumulator */
2503
+ nk_define_cross_pack_size_(dots, bf16, serial, bf16, bf16, /*norm_value_type=*/f32, /*depth_simd_dimensions=*/8,
2504
+ /*dimensions_per_value=*/1)
2505
+ nk_define_cross_pack_(dots, bf16, serial, bf16, bf16, nk_b128_vec_t, nk_load_b128_serial_,
2506
+ nk_partial_load_b16x8_serial_, nk_store_b128_serial_, nk_partial_store_b16x8_serial_,
2507
+ /*simd_width=*/8, /*norm_value_type=*/f32, nk_dots_reduce_sumsq_bf16_,
2508
+ /*depth_simd_dimensions=*/8, /*dimensions_per_value=*/1)
2509
+ nk_define_cross_symmetric_(dots, bf16, serial, bf16, f32, nk_b128_vec_t, nk_dot_bf16x8_state_serial_t, nk_b128_vec_t,
2510
+ nk_dot_bf16x8_init_serial, nk_load_b128_serial_, nk_partial_load_b16x8_serial_,
2511
+ nk_dot_bf16x8_update_serial, nk_dot_bf16x8_finalize_serial, nk_store_b128_serial_,
2512
+ nk_partial_store_b32x4_serial_,
2513
+ /*depth_simd_dimensions=*/8, /*dimensions_per_value=*/1)
2514
+ nk_define_cross_packed_(dots, bf16, serial, bf16, bf16, f32, nk_b128_vec_t, nk_dot_bf16x8_state_serial_t, nk_b128_vec_t,
2515
+ nk_dot_bf16x8_init_serial, nk_load_b128_serial_, nk_partial_load_b16x8_serial_,
2516
+ nk_load_b128_serial_, nk_partial_load_b16x8_serial_, nk_dot_bf16x8_update_serial,
2517
+ nk_dot_bf16x8_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2518
+ /*depth_simd_dimensions=*/8, /*dimensions_per_value=*/1)
2519
+
2520
+ /* I8 GEMM: depth_simd_dimensions=16 (16 i8s = 16 bytes), I32 accumulator */
2521
+ nk_define_cross_pack_size_(dots, i8, serial, i8, i8, /*norm_value_type=*/u32, /*depth_simd_dimensions=*/16,
2522
+ /*dimensions_per_value=*/1)
2523
+ nk_define_cross_pack_(dots, i8, serial, i8, i8, nk_b128_vec_t, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2524
+ nk_store_b128_serial_, nk_partial_store_b8x16_serial_, /*simd_width=*/16,
2525
+ /*norm_value_type=*/u32, nk_dots_reduce_sumsq_i8_, /*depth_simd_dimensions=*/16,
2526
+ /*dimensions_per_value=*/1)
2527
+ nk_define_cross_symmetric_(dots, i8, serial, i8, i32, nk_b128_vec_t, nk_dot_i8x16_state_serial_t, nk_b128_vec_t,
2528
+ nk_dot_i8x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2529
+ nk_dot_i8x16_update_serial, nk_dot_i8x16_finalize_serial, nk_store_b128_serial_,
2530
+ nk_partial_store_b32x4_serial_,
2531
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2532
+ nk_define_cross_packed_(dots, i8, serial, i8, i8, i32, nk_b128_vec_t, nk_dot_i8x16_state_serial_t, nk_b128_vec_t,
2533
+ nk_dot_i8x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2534
+ nk_load_b128_serial_, nk_partial_load_b8x16_serial_, nk_dot_i8x16_update_serial,
2535
+ nk_dot_i8x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2536
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2537
+
2538
+ /* U8 GEMM: depth_simd_dimensions=16 (16 u8s = 16 bytes), U32 accumulator */
2539
+ nk_define_cross_pack_size_(dots, u8, serial, u8, u8, /*norm_value_type=*/u32, /*depth_simd_dimensions=*/16,
2540
+ /*dimensions_per_value=*/1)
2541
+ nk_define_cross_pack_(dots, u8, serial, u8, u8, nk_b128_vec_t, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2542
+ nk_store_b128_serial_, nk_partial_store_b8x16_serial_, /*simd_width=*/16,
2543
+ /*norm_value_type=*/u32, nk_dots_reduce_sumsq_u8_, /*depth_simd_dimensions=*/16,
2544
+ /*dimensions_per_value=*/1)
2545
+ nk_define_cross_symmetric_(dots, u8, serial, u8, u32, nk_b128_vec_t, nk_dot_u8x16_state_serial_t, nk_b128_vec_t,
2546
+ nk_dot_u8x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2547
+ nk_dot_u8x16_update_serial, nk_dot_u8x16_finalize_serial, nk_store_b128_serial_,
2548
+ nk_partial_store_b32x4_serial_,
2549
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2550
+ nk_define_cross_packed_(dots, u8, serial, u8, u8, u32, nk_b128_vec_t, nk_dot_u8x16_state_serial_t, nk_b128_vec_t,
2551
+ nk_dot_u8x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2552
+ nk_load_b128_serial_, nk_partial_load_b8x16_serial_, nk_dot_u8x16_update_serial,
2553
+ nk_dot_u8x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2554
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2555
+
2556
+ /* E4M3 GEMM: depth_simd_dimensions=16 (16 e4m3s = 16 bytes), F32 accumulator */
2557
+ nk_define_cross_pack_size_(dots, e4m3, serial, e4m3, e4m3, /*norm_value_type=*/f32, /*depth_simd_dimensions=*/16,
2558
+ /*dimensions_per_value=*/1)
2559
+ nk_define_cross_pack_(dots, e4m3, serial, e4m3, e4m3, nk_b128_vec_t, nk_load_b128_serial_,
2560
+ nk_partial_load_b8x16_serial_, nk_store_b128_serial_, nk_partial_store_b8x16_serial_,
2561
+ /*simd_width=*/16, /*norm_value_type=*/f32, nk_dots_reduce_sumsq_e4m3_,
2562
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2563
+ nk_define_cross_symmetric_(dots, e4m3, serial, e4m3, f32, nk_b128_vec_t, nk_dot_e4m3x16_state_serial_t, nk_b128_vec_t,
2564
+ nk_dot_e4m3x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2565
+ nk_dot_e4m3x16_update_serial, nk_dot_e4m3x16_finalize_serial, nk_store_b128_serial_,
2566
+ nk_partial_store_b32x4_serial_,
2567
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2568
+ nk_define_cross_packed_(dots, e4m3, serial, e4m3, e4m3, f32, nk_b128_vec_t, nk_dot_e4m3x16_state_serial_t,
2569
+ nk_b128_vec_t, nk_dot_e4m3x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2570
+ nk_load_b128_serial_, nk_partial_load_b8x16_serial_, nk_dot_e4m3x16_update_serial,
2571
+ nk_dot_e4m3x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2572
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2573
+
2574
+ /* E5M2 GEMM: depth_simd_dimensions=16 (16 e5m2s = 16 bytes), F32 accumulator */
2575
+ nk_define_cross_pack_size_(dots, e5m2, serial, e5m2, e5m2, /*norm_value_type=*/f32, /*depth_simd_dimensions=*/16,
2576
+ /*dimensions_per_value=*/1)
2577
+ nk_define_cross_pack_(dots, e5m2, serial, e5m2, e5m2, nk_b128_vec_t, nk_load_b128_serial_,
2578
+ nk_partial_load_b8x16_serial_, nk_store_b128_serial_, nk_partial_store_b8x16_serial_,
2579
+ /*simd_width=*/16, /*norm_value_type=*/f32, nk_dots_reduce_sumsq_e5m2_,
2580
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2581
+ nk_define_cross_symmetric_(dots, e5m2, serial, e5m2, f32, nk_b128_vec_t, nk_dot_e5m2x16_state_serial_t, nk_b128_vec_t,
2582
+ nk_dot_e5m2x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2583
+ nk_dot_e5m2x16_update_serial, nk_dot_e5m2x16_finalize_serial, nk_store_b128_serial_,
2584
+ nk_partial_store_b32x4_serial_,
2585
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2586
+ nk_define_cross_packed_(dots, e5m2, serial, e5m2, e5m2, f32, nk_b128_vec_t, nk_dot_e5m2x16_state_serial_t,
2587
+ nk_b128_vec_t, nk_dot_e5m2x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2588
+ nk_load_b128_serial_, nk_partial_load_b8x16_serial_, nk_dot_e5m2x16_update_serial,
2589
+ nk_dot_e5m2x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2590
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2591
+
2592
+ /* E2M3 GEMM: depth_simd_dimensions=16 (16 e2m3s = 16 bytes), F32 accumulator */
2593
+ nk_define_cross_pack_size_(dots, e2m3, serial, e2m3, e2m3, /*norm_value_type=*/f32, /*depth_simd_dimensions=*/16,
2594
+ /*dimensions_per_value=*/1)
2595
+ nk_define_cross_pack_(dots, e2m3, serial, e2m3, e2m3, nk_b128_vec_t, nk_load_b128_serial_,
2596
+ nk_partial_load_b8x16_serial_, nk_store_b128_serial_, nk_partial_store_b8x16_serial_,
2597
+ /*simd_width=*/16, /*norm_value_type=*/f32, nk_dots_reduce_sumsq_e2m3_,
2598
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2599
+ nk_define_cross_symmetric_(dots, e2m3, serial, e2m3, f32, nk_b128_vec_t, nk_dot_e2m3x16_state_serial_t, nk_b128_vec_t,
2600
+ nk_dot_e2m3x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2601
+ nk_dot_e2m3x16_update_serial, nk_dot_e2m3x16_finalize_serial, nk_store_b128_serial_,
2602
+ nk_partial_store_b32x4_serial_,
2603
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2604
+ nk_define_cross_packed_(dots, e2m3, serial, e2m3, e2m3, f32, nk_b128_vec_t, nk_dot_e2m3x16_state_serial_t,
2605
+ nk_b128_vec_t, nk_dot_e2m3x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2606
+ nk_load_b128_serial_, nk_partial_load_b8x16_serial_, nk_dot_e2m3x16_update_serial,
2607
+ nk_dot_e2m3x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2608
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2609
+
2610
+ /* E3M2 GEMM: depth_simd_dimensions=16 (16 e3m2s = 16 bytes), F32 accumulator */
2611
+ nk_define_cross_pack_size_(dots, e3m2, serial, e3m2, e3m2, /*norm_value_type=*/f32, /*depth_simd_dimensions=*/16,
2612
+ /*dimensions_per_value=*/1)
2613
+ nk_define_cross_pack_(dots, e3m2, serial, e3m2, e3m2, nk_b128_vec_t, nk_load_b128_serial_,
2614
+ nk_partial_load_b8x16_serial_, nk_store_b128_serial_, nk_partial_store_b8x16_serial_,
2615
+ /*simd_width=*/16, /*norm_value_type=*/f32, nk_dots_reduce_sumsq_e3m2_,
2616
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2617
+ nk_define_cross_symmetric_(dots, e3m2, serial, e3m2, f32, nk_b128_vec_t, nk_dot_e3m2x16_state_serial_t, nk_b128_vec_t,
2618
+ nk_dot_e3m2x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2619
+ nk_dot_e3m2x16_update_serial, nk_dot_e3m2x16_finalize_serial, nk_store_b128_serial_,
2620
+ nk_partial_store_b32x4_serial_,
2621
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2622
+ nk_define_cross_packed_(dots, e3m2, serial, e3m2, e3m2, f32, nk_b128_vec_t, nk_dot_e3m2x16_state_serial_t,
2623
+ nk_b128_vec_t, nk_dot_e3m2x16_init_serial, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2624
+ nk_load_b128_serial_, nk_partial_load_b8x16_serial_, nk_dot_e3m2x16_update_serial,
2625
+ nk_dot_e3m2x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2626
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/1)
2627
+
2628
+ /* U4 GEMM: u4x2 for both A and B */
2629
+ nk_define_cross_pack_size_(dots, u4, serial, u4x2, u4x2, /*norm_value_type=*/u32, /*depth_simd_dimensions=*/16,
2630
+ /*dimensions_per_value=*/2)
2631
+ nk_define_cross_pack_(dots, u4, serial, u4x2, u4x2, nk_b128_vec_t, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2632
+ nk_store_b128_serial_, nk_partial_store_b8x16_serial_, /*simd_width=*/16,
2633
+ /*norm_value_type=*/u32, nk_dots_reduce_sumsq_u4_, /*depth_simd_dimensions=*/16,
2634
+ /*dimensions_per_value=*/2)
2635
+ nk_define_cross_symmetric_(dots, u4, serial, u4x2, u32, nk_b64_vec_t, nk_dot_u4x16_state_serial_t, nk_b128_vec_t,
2636
+ nk_dot_u4x16_init_serial, nk_load_b64_serial_, nk_partial_load_b4x16_serial_,
2637
+ nk_dot_u4x16_update_serial, nk_dot_u4x16_finalize_serial, nk_store_b128_serial_,
2638
+ nk_partial_store_b32x4_serial_,
2639
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/2)
2640
+ nk_define_cross_packed_(dots, u4, serial, u4x2, u4x2, u32, nk_b64_vec_t, nk_dot_u4x16_state_serial_t, nk_b128_vec_t,
2641
+ nk_dot_u4x16_init_serial, nk_load_b64_serial_, nk_partial_load_b4x16_serial_,
2642
+ nk_load_b64_serial_, nk_partial_load_b4x16_serial_, nk_dot_u4x16_update_serial,
2643
+ nk_dot_u4x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2644
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/2)
2645
+
2646
+ /* I4 GEMM: i4x2 for both A and B */
2647
+ nk_define_cross_pack_size_(dots, i4, serial, i4x2, i4x2, /*norm_value_type=*/u32, /*depth_simd_dimensions=*/16,
2648
+ /*dimensions_per_value=*/2)
2649
+ nk_define_cross_pack_(dots, i4, serial, i4x2, i4x2, nk_b128_vec_t, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2650
+ nk_store_b128_serial_, nk_partial_store_b8x16_serial_, /*simd_width=*/16,
2651
+ /*norm_value_type=*/u32, nk_dots_reduce_sumsq_i4_, /*depth_simd_dimensions=*/16,
2652
+ /*dimensions_per_value=*/2)
2653
+ nk_define_cross_symmetric_(dots, i4, serial, i4x2, i32, nk_b64_vec_t, nk_dot_i4x16_state_serial_t, nk_b128_vec_t,
2654
+ nk_dot_i4x16_init_serial, nk_load_b64_serial_, nk_partial_load_b4x16_serial_,
2655
+ nk_dot_i4x16_update_serial, nk_dot_i4x16_finalize_serial, nk_store_b128_serial_,
2656
+ nk_partial_store_b32x4_serial_,
2657
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/2)
2658
+ nk_define_cross_packed_(dots, i4, serial, i4x2, i4x2, i32, nk_b64_vec_t, nk_dot_i4x16_state_serial_t, nk_b128_vec_t,
2659
+ nk_dot_i4x16_init_serial, nk_load_b64_serial_, nk_partial_load_b4x16_serial_,
2660
+ nk_load_b64_serial_, nk_partial_load_b4x16_serial_, nk_dot_i4x16_update_serial,
2661
+ nk_dot_i4x16_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2662
+ /*depth_simd_dimensions=*/16, /*dimensions_per_value=*/2)
2663
+
2664
+ /* U1 GEMM: u1x8 for both A and B */
2665
+ nk_define_cross_pack_size_(dots, u1, serial, u1x8, u1x8, /*norm_value_type=*/u32, /*depth_simd_dimensions=*/128,
2666
+ /*dimensions_per_value=*/8)
2667
+ nk_define_cross_pack_(dots, u1, serial, u1x8, u1x8, nk_b128_vec_t, nk_load_b128_serial_, nk_partial_load_b8x16_serial_,
2668
+ nk_store_b128_serial_, nk_partial_store_b8x16_serial_, /*simd_width=*/16,
2669
+ /*norm_value_type=*/u32, nk_dots_reduce_sum_u1_, /*depth_simd_dimensions=*/128,
2670
+ /*dimensions_per_value=*/8)
2671
+ nk_define_cross_symmetric_(dots, u1, serial, u1x8, u32, nk_b128_vec_t, nk_dot_u1x128_state_serial_t, nk_b128_vec_t,
2672
+ nk_dot_u1x128_init_serial, nk_load_b128_serial_, nk_partial_load_b1x128_serial_,
2673
+ nk_dot_u1x128_update_serial, nk_dot_u1x128_finalize_serial, nk_store_b128_serial_,
2674
+ nk_partial_store_b32x4_serial_,
2675
+ /*depth_simd_dimensions=*/128, /*dimensions_per_value=*/8)
2676
+ nk_define_cross_packed_(dots, u1, serial, u1x8, u1x8, u32, nk_b128_vec_t, nk_dot_u1x128_state_serial_t, nk_b128_vec_t,
2677
+ nk_dot_u1x128_init_serial, nk_load_b128_serial_, nk_partial_load_b1x128_serial_,
2678
+ nk_load_b128_serial_, nk_partial_load_b1x128_serial_, nk_dot_u1x128_update_serial,
2679
+ nk_dot_u1x128_finalize_serial, nk_store_b128_serial_, nk_partial_store_b32x4_serial_,
2680
+ /*depth_simd_dimensions=*/128, /*dimensions_per_value=*/8)
2681
+
2682
+ #if defined(NDEBUG)
2683
+ #if defined(_MSC_VER)
2684
+ #pragma optimize("", on)
2685
+ #elif defined(__clang__)
2686
+ #pragma clang attribute pop
2687
+ #elif defined(__GNUC__)
2688
+ #pragma GCC pop_options
2689
+ #endif
2690
+ #endif
2691
+
2692
+ /* BF16 compact: truncate F32 → BF16 in-place.
2693
+ * Reads F32 matrix with c_stride_in_bytes, writes BF16 tightly packed (stride_in_bytes = column_count × sizeof(bf16)).
2694
+ */
2695
+ NK_PUBLIC void nk_dots_compact_bf16_serial(void *c, nk_size_t row_count, nk_size_t column_count,
2696
+ nk_size_t c_stride_in_bytes) {
2697
+ nk_size_t const c_stride_in_values = c_stride_in_bytes / sizeof(nk_f32_t);
2698
+ nk_f32_t const *c_f32 = (nk_f32_t const *)c;
2699
+ nk_bf16_t *c_bf16 = (nk_bf16_t *)c;
2700
+
2701
+ for (nk_size_t row_index = 0; row_index < row_count; row_index++) {
2702
+ nk_f32_t const *source_row = c_f32 + row_index * c_stride_in_values;
2703
+ nk_bf16_t *destination_row = c_bf16 + row_index * column_count;
2704
+ for (nk_size_t column_index = 0; column_index < column_count; column_index++) {
2705
+ nk_f32_to_bf16_serial(source_row + column_index, destination_row + column_index);
2706
+ }
2707
+ }
2708
+ }
2709
+
2710
+ /* I8 compact: re-normalize I32 → I8 using precomputed squared norms.
2711
+ * Formula: c_i8[i][j] = c_i32[i][j] × 127 / sqrt(a_norm[i] × b_norm[j])
2712
+ * Output is tightly packed (stride_in_bytes = column_count × sizeof(i8)).
2713
+ */
2714
+ NK_PUBLIC void nk_dots_compact_i8_serial(void *c, nk_size_t row_count, nk_size_t column_count,
2715
+ nk_size_t c_stride_in_bytes, nk_i32_t const *a_squared_norms,
2716
+ nk_i32_t const *b_squared_norms) {
2717
+ nk_size_t const c_stride_in_values = c_stride_in_bytes / sizeof(nk_i32_t);
2718
+ nk_i32_t const *c_i32 = (nk_i32_t const *)c;
2719
+ nk_i8_t *c_i8 = (nk_i8_t *)c;
2720
+
2721
+ for (nk_size_t row_index = 0; row_index < row_count; row_index++) {
2722
+ nk_i32_t const *source_row = c_i32 + row_index * c_stride_in_values;
2723
+ nk_i8_t *destination_row = c_i8 + row_index * column_count;
2724
+
2725
+ nk_f32_t a_norm_f32_value = (nk_f32_t)a_squared_norms[row_index];
2726
+ nk_f32_t a_rsqrt_value = (a_norm_f32_value > 0) ? (1.0f / nk_f32_sqrt_serial(a_norm_f32_value)) : 0.0f;
2727
+
2728
+ for (nk_size_t column_index = 0; column_index < column_count; column_index++) {
2729
+ nk_f32_t b_norm_f32_value = (nk_f32_t)b_squared_norms[column_index];
2730
+ nk_f32_t b_rsqrt_value = (b_norm_f32_value > 0) ? (1.0f / nk_f32_sqrt_serial(b_norm_f32_value)) : 0.0f;
2731
+
2732
+ nk_f32_t normalized_value = (nk_f32_t)source_row[column_index] * 127.0f * a_rsqrt_value * b_rsqrt_value;
2733
+ nk_i32_t clamped_value = (nk_i32_t)normalized_value;
2734
+ if (clamped_value < -128) clamped_value = -128;
2735
+ if (clamped_value > 127) clamped_value = 127;
2736
+ destination_row[column_index] = (nk_i8_t)clamped_value;
2737
+ }
2738
+ }
2739
+ }
2740
+
2741
+ #define nk_define_cross_normalized_packed_(metric_name, input_type_name, isa_suffix, input_value_type, \
2742
+ packed_value_type, dot_result_type, norm_value_type, final_result_type, \
2743
+ vec_type, dots_packed_fn, from_dot_fn, compute_norm_fn, load_fn, \
2744
+ partial_load_fn, store_fn, partial_store_fn, dimensions_per_value) \
2745
+ NK_PUBLIC void nk_##metric_name##s_packed_##input_type_name##_##isa_suffix( \
2746
+ nk_##input_value_type##_t const *a_matrix, void const *b_packed_buffer, nk_##final_result_type##_t *c_matrix, \
2747
+ nk_size_t row_count, nk_size_t column_count, nk_size_t depth, nk_size_t a_stride_in_bytes, \
2748
+ nk_size_t c_stride_in_bytes) { \
2749
+ \
2750
+ dots_packed_fn(a_matrix, b_packed_buffer, (nk_##dot_result_type##_t *)c_matrix, row_count, column_count, \
2751
+ depth, a_stride_in_bytes, c_stride_in_bytes); \
2752
+ \
2753
+ nk_cross_packed_buffer_header_t const *header = (nk_cross_packed_buffer_header_t const *)b_packed_buffer; \
2754
+ nk_size_t depth_padded = header->depth_padded_values; \
2755
+ nk_##norm_value_type##_t const *b_norms = \
2756
+ (nk_##norm_value_type##_t const *)((char const *)b_packed_buffer + \
2757
+ sizeof(nk_cross_packed_buffer_header_t) + \
2758
+ column_count * depth_padded * sizeof(nk_##packed_value_type##_t)); \
2759
+ \
2760
+ for (nk_size_t row_index = 0; row_index < row_count; ++row_index) { \
2761
+ nk_##input_value_type##_t const *a_row = \
2762
+ (nk_##input_value_type##_t const *)((char const *)a_matrix + row_index * a_stride_in_bytes); \
2763
+ nk_##dot_result_type##_t query_norm = compute_norm_fn(a_row, depth); \
2764
+ nk_##dot_result_type##_t *r_row_dots = (nk_##dot_result_type##_t *)((char *)c_matrix + \
2765
+ row_index * c_stride_in_bytes); \
2766
+ nk_##final_result_type##_t *r_row_out = (nk_##final_result_type##_t *)((char *)c_matrix + \
2767
+ row_index * c_stride_in_bytes); \
2768
+ \
2769
+ nk_size_t column_index = 0; \
2770
+ for (; column_index + 4 <= column_count; column_index += 4) { \
2771
+ vec_type dots_vec, norms_vec, results_vec; \
2772
+ load_fn(r_row_dots + column_index, &dots_vec); \
2773
+ load_fn(b_norms + column_index, &norms_vec); \
2774
+ from_dot_fn(dots_vec, query_norm, norms_vec, &results_vec); \
2775
+ store_fn(&results_vec, r_row_out + column_index); \
2776
+ } \
2777
+ if (column_index < column_count) { \
2778
+ vec_type dots_vec = {0}, norms_vec = {0}, results_vec; \
2779
+ partial_load_fn(r_row_dots + column_index, &dots_vec, column_count - column_index); \
2780
+ partial_load_fn(b_norms + column_index, &norms_vec, column_count - column_index); \
2781
+ from_dot_fn(dots_vec, query_norm, norms_vec, &results_vec); \
2782
+ partial_store_fn(&results_vec, r_row_out + column_index, column_count - column_index); \
2783
+ } \
2784
+ } \
2785
+ }
2786
+
2787
+ #define nk_define_cross_normalized_symmetric_(metric_name, input_type_name, isa_suffix, input_value_type, \
2788
+ dot_result_type, norm_value_type, final_result_type, vec_type, \
2789
+ dots_symmetric_fn, from_dot_fn, compute_norm_fn, load_fn, \
2790
+ partial_load_fn, store_fn, partial_store_fn, dimensions_per_value) \
2791
+ NK_PUBLIC void nk_##metric_name##s_symmetric_##input_type_name##_##isa_suffix( \
2792
+ nk_##input_value_type##_t const *vectors, nk_size_t vectors_count, nk_size_t depth, nk_size_t stride_in_bytes, \
2793
+ nk_##final_result_type##_t *result, nk_size_t result_stride_in_bytes, nk_size_t row_start, \
2794
+ nk_size_t row_count) { \
2795
+ \
2796
+ dots_symmetric_fn(vectors, vectors_count, depth, stride_in_bytes, (nk_##dot_result_type##_t *)result, \
2797
+ result_stride_in_bytes, row_start, row_count); \
2798
+ \
2799
+ /* Phase 1 — cache row norms in the result diagonal (O(row_count) calls) */ \
2800
+ for (nk_size_t row_index = row_start; row_index < row_start + row_count; ++row_index) { \
2801
+ nk_##input_value_type##_t const *row_vector = \
2802
+ (nk_##input_value_type##_t const *)((char const *)vectors + row_index * stride_in_bytes); \
2803
+ nk_##norm_value_type##_t *row_diag = (nk_##norm_value_type##_t *)((char *)result + \
2804
+ row_index * result_stride_in_bytes); \
2805
+ row_diag[row_index] = compute_norm_fn(row_vector, depth); \
2806
+ } \
2807
+ \
2808
+ /* Phase 2 — column-first post-processing with 256-element norm cache */ \
2809
+ nk_##norm_value_type##_t column_norms[256]; \
2810
+ for (nk_size_t column_chunk_start = 0; column_chunk_start < vectors_count; column_chunk_start += 256) { \
2811
+ nk_size_t column_chunk_end = column_chunk_start + 256 < vectors_count ? column_chunk_start + 256 \
2812
+ : vectors_count; \
2813
+ \
2814
+ /* Pre-compute norms for this column chunk — each column visited exactly once */ \
2815
+ for (nk_size_t col = column_chunk_start; col < column_chunk_end; ++col) { \
2816
+ nk_##input_value_type##_t const *column_vector = \
2817
+ (nk_##input_value_type##_t const *)((char const *)vectors + col * stride_in_bytes); \
2818
+ column_norms[col - column_chunk_start] = compute_norm_fn(column_vector, depth); \
2819
+ } \
2820
+ \
2821
+ /* Sweep assigned rows against this column chunk */ \
2822
+ for (nk_size_t row_index = row_start; row_index < row_start + row_count; ++row_index) { \
2823
+ nk_size_t j_start = row_index + 1 > column_chunk_start ? row_index + 1 : column_chunk_start; \
2824
+ if (j_start >= column_chunk_end) continue; \
2825
+ char *row_ptr = (char *)result + row_index * result_stride_in_bytes; \
2826
+ nk_##norm_value_type##_t sumsq_i = ((nk_##norm_value_type##_t *)row_ptr)[row_index]; \
2827
+ nk_##dot_result_type##_t *r_dots = (nk_##dot_result_type##_t *)row_ptr; \
2828
+ nk_##final_result_type##_t *r_out = (nk_##final_result_type##_t *)row_ptr; \
2829
+ \
2830
+ /* 4-wide vectorized loop */ \
2831
+ nk_size_t j = j_start; \
2832
+ for (; j + 4 <= column_chunk_end; j += 4) { \
2833
+ vec_type target_norms_vec; \
2834
+ load_fn(&column_norms[j - column_chunk_start], &target_norms_vec); \
2835
+ vec_type dots_vec, results_vec; \
2836
+ load_fn(r_dots + j, &dots_vec); \
2837
+ from_dot_fn(dots_vec, sumsq_i, target_norms_vec, &results_vec); \
2838
+ store_fn(&results_vec, r_out + j); \
2839
+ } \
2840
+ /* Remainder */ \
2841
+ if (j < column_chunk_end) { \
2842
+ vec_type dots_vec = {0}, norms_vec = {0}, results_vec; \
2843
+ partial_load_fn(r_dots + j, &dots_vec, column_chunk_end - j); \
2844
+ partial_load_fn(&column_norms[j - column_chunk_start], &norms_vec, column_chunk_end - j); \
2845
+ from_dot_fn(dots_vec, sumsq_i, norms_vec, &results_vec); \
2846
+ partial_store_fn(&results_vec, r_out + j, column_chunk_end - j); \
2847
+ } \
2848
+ } \
2849
+ } \
2850
+ \
2851
+ /* Phase 3 — zero diagonals */ \
2852
+ for (nk_size_t row_index = row_start; row_index < row_start + row_count; ++row_index) { \
2853
+ nk_##final_result_type##_t *r_out = (nk_##final_result_type##_t *)((char *)result + \
2854
+ row_index * result_stride_in_bytes); \
2855
+ r_out[row_index] = 0; \
2856
+ } \
2857
+ }
2858
+
2859
+ #if defined(__cplusplus)
2860
+ } // extern "C"
2861
+ #endif
2862
+
2863
+ #if defined(__GNUC__) && !defined(__clang__)
2864
+ #pragma GCC diagnostic pop
2865
+ #endif
2866
+
2867
+ #endif // NK_DOTS_SERIAL_H