usearch 2.21.3 → 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 +235 -52
  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,1363 @@
1
+ /**
2
+ * @brief FlashAttention-style kernels for Intel Sapphire Rapids AMX.
3
+ * @file include/numkong/attention/sapphireamx.h
4
+ * @author Ash Vardanian
5
+ * @date January 5, 2026
6
+ *
7
+ * @sa include/numkong/attention.h
8
+ *
9
+ * This file implements FlashAttention-2 style scaled dot-product attention (SDPA) optimized
10
+ * for Intel AMX instructions on Sapphire Rapids CPUs. The kernel computes:
11
+ *
12
+ * O = softmax(Q × Kᵀ / √d) × V
13
+ *
14
+ * Key features:
15
+ * - Online softmax: Mathematically exact, processes KV blocks incrementally
16
+ * - Pre-packed KV cache: Amortizes packing cost for repeated inference
17
+ * - GQA/MQA support: Different num_heads and num_kv_heads for grouped-query attention
18
+ * - Causal masking: Optional masking for autoregressive generation
19
+ *
20
+ * Target models (2025):
21
+ * - Kimi K2: head_dim=112, 64 heads, MHA, 128K context
22
+ * - LLaMA 3.1 405B: head_dim=128, 128 heads, 16 KV heads (GQA 8:1), 128K context
23
+ * - Qwen 2.5 72B: head_dim=128, 64 heads, 8 KV heads (GQA 8:1), 32K context
24
+ *
25
+ * Performance comparison with H100 FlashAttention-2:
26
+ * - H100 SXM5: ~335 TFLOPS (35% of 989 TFLOPS peak), 80GB HBM3
27
+ * - 100-core SPR: ~40 TFLOPS with FlashAttention (13% of 300 TFLOPS peak)
28
+ * - CPU advantage: 512GB-2TB DDR5 vs 80GB HBM → supports 10-25⨯ longer contexts
29
+ *
30
+ * Expected performance per core:
31
+ * - Decode (query_len=1, kv_len=4K): 350-450 GOPS (softmax bound)
32
+ * - Prefill (query_len=64, kv_len=4K): 450-550 GOPS (better AMX utilization)
33
+ * - Long context (kv_len=64K+): 250-350 GOPS (memory bandwidth bound)
34
+ *
35
+ * Block sizes:
36
+ * - Bᵣ = 16 (query block rows, matches AMX tile height)
37
+ * - Bᶜ = 16 (KV block columns, fits 16×16 scores in 16 ZMM registers)
38
+ *
39
+ * Algorithm (FlashAttention-2 style):
40
+ * For each query block:
41
+ * Initialize O = 0, rowsum = 0, rowmax = -∞
42
+ * For each KV block:
43
+ * S = Q × Kᵀ using AMX TDPBF16PS
44
+ * Apply online softmax: rescale old values, accumulate new
45
+ * O = rescale(O) + P × V using AMX
46
+ * Finalize: normalize O by row sums
47
+ *
48
+ * @section sapphireamx_attention_instructions Relevant Instructions
49
+ *
50
+ * Intrinsic Instruction Sapphire
51
+ * _tile_dpbf16ps TDPBF16PS (TMM, TMM, TMM) ~16cy (16x16x32 BF16)
52
+ * _tile_dpbssd TDPBSSD (TMM, TMM, TMM) ~16cy (16x16x64 INT8)
53
+ * _tile_loadd TILELOADD (TMM, MEM) ~10cy @ p23
54
+ * _tile_stored TILESTORED (MEM, TMM) ~10cy @ p4
55
+ * _tile_zero TILEZERO (TMM) ~1cy
56
+ * _mm512_fmadd_ps VFMADD (ZMM, ZMM, ZMM) 4cy @ p05
57
+ * _mm512_mul_ps VMULPS (ZMM, ZMM, ZMM) 4cy @ p05
58
+ * _mm512_max_ps VMAXPS (ZMM, ZMM, ZMM) 4cy @ p05
59
+ * _mm512_reduce_max_ps (pseudo: VHADDPS chain) ~8cy
60
+ * _mm512_reduce_add_ps (pseudo: VHADDPS chain) ~8cy
61
+ */
62
+ #ifndef NK_ATTENTION_SAPPHIREAMX_H
63
+ #define NK_ATTENTION_SAPPHIREAMX_H
64
+
65
+ #if NK_TARGET_X8664_
66
+ #if NK_TARGET_SAPPHIREAMX
67
+
68
+ #include "numkong/types.h"
69
+ #include "numkong/dots/sapphireamx.h"
70
+
71
+ #if defined(__cplusplus)
72
+ extern "C" {
73
+ #endif
74
+
75
+ #if defined(__clang__)
76
+ #pragma clang attribute push( \
77
+ __attribute__((target("avx2,avx512f,avx512vl,avx512bw,avx512dq,avx512fp16,avx512bf16,f16c,fma,bmi,bmi2"))), \
78
+ apply_to = function)
79
+ #elif defined(__GNUC__)
80
+ #pragma GCC push_options
81
+ #pragma GCC target("avx2", "avx512f", "avx512vl", "avx512bw", "avx512dq", "avx512fp16", "avx512bf16", "f16c", "fma", \
82
+ "bmi", "bmi2")
83
+ #endif
84
+
85
+ /**
86
+ * @brief Packed KV cache header for attention (64-byte aligned).
87
+ *
88
+ * Layout in memory:
89
+ * [header: 64 bytes][K tiles: variable][V tiles: variable]
90
+ *
91
+ * K and V are packed in AMX tile format for efficient loading.
92
+ */
93
+ typedef struct {
94
+ nk_u32_t num_kv_heads; ///< Number of K/V heads (for GQA, may differ from Q heads)
95
+ nk_u32_t head_dim; ///< Original head dimension (64, 112, 128)
96
+ nk_u32_t head_dim_padded; ///< Padded to multiple of 32 for AMX tiles
97
+ nk_u32_t seq_len; ///< Current sequence length
98
+ nk_u32_t max_seq_len; ///< Maximum sequence length (for pre-allocation)
99
+ nk_u32_t k_offset; ///< Byte offset to K tiles from header start
100
+ nk_u32_t v_offset; ///< Byte offset to V tiles from header start
101
+ nk_u32_t reserved[9]; ///< Pad to 64 bytes
102
+ } nk_attention_kv_packed_header_t;
103
+
104
+ /**
105
+ * @brief Fast exp approximation for AVX-512.
106
+ *
107
+ * Uses Cody-Waite range reduction + Remez minimax polynomial.
108
+ * Accuracy: max error < 1 ULP for x ∈ [-87.3, 88.7] (float range).
109
+ * Performance: ~15-20 cycles for 16 floats.
110
+ */
111
+
112
+ /**
113
+ * @brief Fast vectorized exp(x) approximation using AVX-512.
114
+ *
115
+ * Algorithm:
116
+ * 1. Range reduction: x = n × ln(2) + r, where |r| < ln(2)/2
117
+ * 2. Polynomial approximation: exp(r) ≈ 1 + r + r²/2 + ... (degree 6)
118
+ * 3. Reconstruction: exp(x) = 2ⁿ × exp(r)
119
+ *
120
+ * @param x Input vector (16 floats)
121
+ * @return exp(x) for each element
122
+ */
123
+ NK_INTERNAL __m512 nk_exp_ps_avx512_(__m512 x_f32x16) {
124
+ // Constants for Cody-Waite range reduction
125
+ const __m512 log2e_f32x16 = _mm512_set1_ps(1.4426950408889634f);
126
+ const __m512 ln2_high_f32x16 = _mm512_set1_ps(0.693145751953125f);
127
+ const __m512 ln2_low_f32x16 = _mm512_set1_ps(1.42860682030941723212e-6f);
128
+
129
+ // Clamp to avoid overflow/underflow
130
+ const __m512 max_x_f32x16 = _mm512_set1_ps(88.3762626647949f);
131
+ const __m512 min_x_f32x16 = _mm512_set1_ps(-87.3365447504021f);
132
+ x_f32x16 = _mm512_max_ps(_mm512_min_ps(x_f32x16, max_x_f32x16), min_x_f32x16);
133
+
134
+ // n_f32x16 = round(x / ln(2))
135
+ __m512 n_f32x16 = _mm512_roundscale_ps(_mm512_mul_ps(x_f32x16, log2e_f32x16), _MM_FROUND_TO_NEAREST_INT);
136
+
137
+ // r_f32x16 = x - n_f32x16 × ln(2) using Cody-Waite for precision
138
+ __m512 r_f32x16 = _mm512_fnmadd_ps(n_f32x16, ln2_high_f32x16, x_f32x16);
139
+ r_f32x16 = _mm512_fnmadd_ps(n_f32x16, ln2_low_f32x16, r_f32x16);
140
+
141
+ // Polynomial approximation for exp(r_f32x16): Remez minimax degree 6
142
+ // Coefficients optimized for [-ln(2)/2, ln(2)/2]
143
+ __m512 p_f32x16 = _mm512_set1_ps(1.9875691500e-4f);
144
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(1.3981999507e-3f));
145
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(8.3334519073e-3f));
146
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(4.1665858030e-2f));
147
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(1.6666665459e-1f));
148
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(5.0000001201e-1f));
149
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(1.0f));
150
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(1.0f));
151
+
152
+ // Reconstruct: exp(x) = 2ⁿ × exp(r_f32x16)
153
+ // 2ⁿ via IEEE 754 exponent manipulation
154
+ __m512i ni_i32x16 = _mm512_cvtps_epi32(n_f32x16);
155
+ ni_i32x16 = _mm512_add_epi32(ni_i32x16, _mm512_set1_epi32(127));
156
+ ni_i32x16 = _mm512_slli_epi32(ni_i32x16, 23);
157
+ __m512 pow2n_f32x16 = _mm512_castsi512_ps(ni_i32x16);
158
+
159
+ return _mm512_mul_ps(p_f32x16, pow2n_f32x16);
160
+ }
161
+
162
+ /**
163
+ * @brief Faster exp(x) approximation using degree-4 polynomial.
164
+ *
165
+ * Trades accuracy for speed: ~0.1% relative error (vs <0.001% for degree-6).
166
+ * This is acceptable for softmax where:
167
+ * - Probabilities sum to 1 (normalization absorbs errors)
168
+ * - Relative ranking matters more than absolute values
169
+ *
170
+ * Performance: ~12-15 cycles for 16 floats (vs ~18-22 for degree-6)
171
+ *
172
+ * @param x Input vector (16 floats)
173
+ * @return exp(x) approximation
174
+ */
175
+ NK_INTERNAL __m512 nk_exp_ps_fast_avx512_(__m512 x_f32x16) {
176
+ // Constants for Cody-Waite range reduction
177
+ const __m512 log2e_f32x16 = _mm512_set1_ps(1.4426950408889634f);
178
+ const __m512 ln2_high_f32x16 = _mm512_set1_ps(0.693145751953125f);
179
+ const __m512 ln2_low_f32x16 = _mm512_set1_ps(1.42860682030941723212e-6f);
180
+
181
+ // Clamp to avoid overflow/underflow (same as accurate version)
182
+ const __m512 max_x_f32x16 = _mm512_set1_ps(88.3762626647949f);
183
+ const __m512 min_x_f32x16 = _mm512_set1_ps(-87.3365447504021f);
184
+ x_f32x16 = _mm512_max_ps(_mm512_min_ps(x_f32x16, max_x_f32x16), min_x_f32x16);
185
+
186
+ // n_f32x16 = round(x / ln(2))
187
+ __m512 n_f32x16 = _mm512_roundscale_ps(_mm512_mul_ps(x_f32x16, log2e_f32x16), _MM_FROUND_TO_NEAREST_INT);
188
+
189
+ // r_f32x16 = x - n_f32x16 × ln(2) using Cody-Waite for precision
190
+ __m512 r_f32x16 = _mm512_fnmadd_ps(n_f32x16, ln2_high_f32x16, x_f32x16);
191
+ r_f32x16 = _mm512_fnmadd_ps(n_f32x16, ln2_low_f32x16, r_f32x16);
192
+
193
+ // Polynomial approximation for exp(r_f32x16): degree 4
194
+ // Optimized coefficients for [-ln(2)/2, ln(2)/2]
195
+ // exp(r_f32x16) ≈ 1 + r_f32x16 + r²/2 + r³/6 + r⁴/24
196
+ // Using Horner form: ((c₄ × r_f32x16 + c₃) × r_f32x16 + c₂) × r_f32x16 + c₁) × r_f32x16 + c₀
197
+ __m512 p_f32x16 = _mm512_set1_ps(4.1666666667e-2f); // 1/24
198
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(1.6666666667e-1f)); // 1/6
199
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(5.0000000000e-1f)); // 1/2
200
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(1.0f)); // 1
201
+ p_f32x16 = _mm512_fmadd_ps(p_f32x16, r_f32x16, _mm512_set1_ps(1.0f)); // 1
202
+
203
+ // Reconstruct: exp(x) = 2ⁿ × exp(r_f32x16)
204
+ __m512i ni_i32x16 = _mm512_cvtps_epi32(n_f32x16);
205
+ ni_i32x16 = _mm512_add_epi32(ni_i32x16, _mm512_set1_epi32(127));
206
+ ni_i32x16 = _mm512_slli_epi32(ni_i32x16, 23);
207
+ __m512 pow2n_f32x16 = _mm512_castsi512_ps(ni_i32x16);
208
+
209
+ return _mm512_mul_ps(p_f32x16, pow2n_f32x16);
210
+ }
211
+
212
+ /**
213
+ * @brief Online softmax primitives.
214
+ *
215
+ * These implement the online softmax algorithm from FlashAttention.
216
+ * Key insight: softmax can be computed incrementally by tracking:
217
+ * - m: running maximum (for numerical stability)
218
+ * - l: running sum of exp(x - m)
219
+ *
220
+ * When a new block arrives with larger values:
221
+ * - Rescale old sum: l = l × exp(m_old - m_new)
222
+ * - Add new contributions: l += Σ exp(x_new - m_new)
223
+ */
224
+
225
+ /**
226
+ * @brief State for online softmax computation.
227
+ *
228
+ * Tracks per-row running maximum and sum for 16 rows.
229
+ */
230
+ typedef struct {
231
+ __m512 row_max_f32x16; ///< Running max per row (16 values)
232
+ __m512 row_sum_f32x16; ///< Running sum of exp(x - max) per row
233
+ } nk_attention_softmax_row_state_t;
234
+
235
+ /**
236
+ * @brief Update softmax state with Bᶜ=32 score block (optimized).
237
+ *
238
+ * Computes online softmax for 16×32 score block using AVX-512.
239
+ * Optimizations:
240
+ * - Process 4 rows at a time for better ILP
241
+ * - Keep scaled scores in registers to avoid reloading
242
+ * - Vectorized row sum accumulation
243
+ */
244
+ NK_INTERNAL void nk_attention_softmax_update_bc32_(nk_attention_softmax_row_state_t *state,
245
+ nk_f32_t const *scores, // [16, 32] score block
246
+ nk_f32_t scale,
247
+ nk_f32_t *weights_out) { // [16, 32] output weights
248
+
249
+ __m512 scale_v_f32x16 = _mm512_set1_ps(scale);
250
+
251
+ // Load and scale all scores, compute per-row max
252
+ // Store in temporary arrays to avoid register pressure
253
+ __m512 s_scaled_f32x16[16][2];
254
+ NK_ALIGN64 float row_maxes[16];
255
+
256
+ // Process 4 rows at a time for ILP
257
+ for (int i = 0; i < 16; i += 4) {
258
+ // Row i
259
+ s_scaled_f32x16[i][0] = _mm512_mul_ps(_mm512_load_ps(scores + i * 32 + 0), scale_v_f32x16);
260
+ s_scaled_f32x16[i][1] = _mm512_mul_ps(_mm512_load_ps(scores + i * 32 + 16), scale_v_f32x16);
261
+ __m512 m0_f32x16 = _mm512_max_ps(s_scaled_f32x16[i][0], s_scaled_f32x16[i][1]);
262
+
263
+ // Row i+1
264
+ s_scaled_f32x16[i + 1][0] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 1) * 32 + 0), scale_v_f32x16);
265
+ s_scaled_f32x16[i + 1][1] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 1) * 32 + 16), scale_v_f32x16);
266
+ __m512 m1_f32x16 = _mm512_max_ps(s_scaled_f32x16[i + 1][0], s_scaled_f32x16[i + 1][1]);
267
+
268
+ // Row i+2
269
+ s_scaled_f32x16[i + 2][0] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 2) * 32 + 0), scale_v_f32x16);
270
+ s_scaled_f32x16[i + 2][1] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 2) * 32 + 16), scale_v_f32x16);
271
+ __m512 m2_f32x16 = _mm512_max_ps(s_scaled_f32x16[i + 2][0], s_scaled_f32x16[i + 2][1]);
272
+
273
+ // Row i+3
274
+ s_scaled_f32x16[i + 3][0] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 3) * 32 + 0), scale_v_f32x16);
275
+ s_scaled_f32x16[i + 3][1] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 3) * 32 + 16), scale_v_f32x16);
276
+ __m512 m3_f32x16 = _mm512_max_ps(s_scaled_f32x16[i + 3][0], s_scaled_f32x16[i + 3][1]);
277
+
278
+ // Reduce to scalar max
279
+ row_maxes[i] = _mm512_reduce_max_ps(m0_f32x16);
280
+ row_maxes[i + 1] = _mm512_reduce_max_ps(m1_f32x16);
281
+ row_maxes[i + 2] = _mm512_reduce_max_ps(m2_f32x16);
282
+ row_maxes[i + 3] = _mm512_reduce_max_ps(m3_f32x16);
283
+ }
284
+
285
+ __m512 row_max_new_f32x16 = _mm512_load_ps(row_maxes);
286
+ __m512 old_max_f32x16 = state->row_max_f32x16;
287
+ __m512 new_max_f32x16 = _mm512_max_ps(old_max_f32x16, row_max_new_f32x16);
288
+
289
+ // Rescale old sum
290
+ __m512 correction_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(old_max_f32x16, new_max_f32x16));
291
+ __m512 new_sum_f32x16 = _mm512_mul_ps(state->row_sum_f32x16, correction_f32x16);
292
+
293
+ // Compute P = exp(S - new_max_f32x16) and accumulate sums
294
+ NK_ALIGN64 nk_f32_t new_max_arr[16];
295
+ NK_ALIGN64 nk_f32_t row_sums[16];
296
+ _mm512_store_ps(new_max_arr, new_max_f32x16);
297
+
298
+ // Process rows
299
+ for (int i = 0; i < 16; i += 2) {
300
+ __m512 max_i_f32x16 = _mm512_set1_ps(new_max_arr[i]);
301
+ __m512 max_i1_f32x16 = _mm512_set1_ps(new_max_arr[i + 1]);
302
+
303
+ // Row i
304
+ __m512 p0_i_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(s_scaled_f32x16[i][0], max_i_f32x16));
305
+ __m512 p1_i_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(s_scaled_f32x16[i][1], max_i_f32x16));
306
+ _mm512_store_ps(weights_out + i * 32 + 0, p0_i_f32x16);
307
+ _mm512_store_ps(weights_out + i * 32 + 16, p1_i_f32x16);
308
+ row_sums[i] = _mm512_reduce_add_ps(p0_i_f32x16) + _mm512_reduce_add_ps(p1_i_f32x16);
309
+
310
+ // Row i+1
311
+ __m512 p0_i1_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(s_scaled_f32x16[i + 1][0], max_i1_f32x16));
312
+ __m512 p1_i1_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(s_scaled_f32x16[i + 1][1], max_i1_f32x16));
313
+ _mm512_store_ps(weights_out + (i + 1) * 32 + 0, p0_i1_f32x16);
314
+ _mm512_store_ps(weights_out + (i + 1) * 32 + 16, p1_i1_f32x16);
315
+ row_sums[i + 1] = _mm512_reduce_add_ps(p0_i1_f32x16) + _mm512_reduce_add_ps(p1_i1_f32x16);
316
+ }
317
+
318
+ // Add row sums to running sum vectorially
319
+ new_sum_f32x16 = _mm512_add_ps(new_sum_f32x16, _mm512_load_ps(row_sums));
320
+
321
+ state->row_max_f32x16 = new_max_f32x16;
322
+ state->row_sum_f32x16 = new_sum_f32x16;
323
+ }
324
+
325
+ /**
326
+ * @brief Fast softmax update using degree-4 exp polynomial.
327
+ *
328
+ * Same algorithm as nk_attention_softmax_update_bc32_ but uses faster exp.
329
+ * Trades ~0.1% accuracy for ~20% performance improvement.
330
+ *
331
+ * Use this for inference where throughput matters more than last-bit accuracy.
332
+ */
333
+ NK_INTERNAL void nk_attention_softmax_update_bc32_fast_(nk_attention_softmax_row_state_t *state,
334
+ nk_f32_t const *scores, // [16, 32] score block
335
+ nk_f32_t scale,
336
+ nk_f32_t *weights_out) { // [16, 32] output weights
337
+
338
+ __m512 scale_v_f32x16 = _mm512_set1_ps(scale);
339
+
340
+ // Load and scale all scores, compute per-row max
341
+ __m512 s_scaled_f32x16[16][2];
342
+ NK_ALIGN64 float row_maxes[16];
343
+
344
+ // Process 4 rows at a time for ILP
345
+ for (int i = 0; i < 16; i += 4) {
346
+ s_scaled_f32x16[i][0] = _mm512_mul_ps(_mm512_load_ps(scores + i * 32 + 0), scale_v_f32x16);
347
+ s_scaled_f32x16[i][1] = _mm512_mul_ps(_mm512_load_ps(scores + i * 32 + 16), scale_v_f32x16);
348
+ __m512 m0_f32x16 = _mm512_max_ps(s_scaled_f32x16[i][0], s_scaled_f32x16[i][1]);
349
+
350
+ s_scaled_f32x16[i + 1][0] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 1) * 32 + 0), scale_v_f32x16);
351
+ s_scaled_f32x16[i + 1][1] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 1) * 32 + 16), scale_v_f32x16);
352
+ __m512 m1_f32x16 = _mm512_max_ps(s_scaled_f32x16[i + 1][0], s_scaled_f32x16[i + 1][1]);
353
+
354
+ s_scaled_f32x16[i + 2][0] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 2) * 32 + 0), scale_v_f32x16);
355
+ s_scaled_f32x16[i + 2][1] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 2) * 32 + 16), scale_v_f32x16);
356
+ __m512 m2_f32x16 = _mm512_max_ps(s_scaled_f32x16[i + 2][0], s_scaled_f32x16[i + 2][1]);
357
+
358
+ s_scaled_f32x16[i + 3][0] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 3) * 32 + 0), scale_v_f32x16);
359
+ s_scaled_f32x16[i + 3][1] = _mm512_mul_ps(_mm512_load_ps(scores + (i + 3) * 32 + 16), scale_v_f32x16);
360
+ __m512 m3_f32x16 = _mm512_max_ps(s_scaled_f32x16[i + 3][0], s_scaled_f32x16[i + 3][1]);
361
+
362
+ row_maxes[i] = _mm512_reduce_max_ps(m0_f32x16);
363
+ row_maxes[i + 1] = _mm512_reduce_max_ps(m1_f32x16);
364
+ row_maxes[i + 2] = _mm512_reduce_max_ps(m2_f32x16);
365
+ row_maxes[i + 3] = _mm512_reduce_max_ps(m3_f32x16);
366
+ }
367
+
368
+ __m512 row_max_new_f32x16 = _mm512_load_ps(row_maxes);
369
+ __m512 old_max_f32x16 = state->row_max_f32x16;
370
+ __m512 new_max_f32x16 = _mm512_max_ps(old_max_f32x16, row_max_new_f32x16);
371
+
372
+ // Rescale old sum using fast exp
373
+ __m512 correction_f32x16 = nk_exp_ps_fast_avx512_(_mm512_sub_ps(old_max_f32x16, new_max_f32x16));
374
+ __m512 new_sum_f32x16 = _mm512_mul_ps(state->row_sum_f32x16, correction_f32x16);
375
+
376
+ // Compute P = exp(S - new_max_f32x16) using fast exp
377
+ NK_ALIGN64 nk_f32_t new_max_arr[16];
378
+ NK_ALIGN64 nk_f32_t row_sums[16];
379
+ _mm512_store_ps(new_max_arr, new_max_f32x16);
380
+
381
+ // Process rows with fast exp
382
+ for (int i = 0; i < 16; i += 2) {
383
+ __m512 max_i_f32x16 = _mm512_set1_ps(new_max_arr[i]);
384
+ __m512 max_i1_f32x16 = _mm512_set1_ps(new_max_arr[i + 1]);
385
+
386
+ // Row i
387
+ __m512 p0_i_f32x16 = nk_exp_ps_fast_avx512_(_mm512_sub_ps(s_scaled_f32x16[i][0], max_i_f32x16));
388
+ __m512 p1_i_f32x16 = nk_exp_ps_fast_avx512_(_mm512_sub_ps(s_scaled_f32x16[i][1], max_i_f32x16));
389
+ _mm512_store_ps(weights_out + i * 32 + 0, p0_i_f32x16);
390
+ _mm512_store_ps(weights_out + i * 32 + 16, p1_i_f32x16);
391
+ row_sums[i] = _mm512_reduce_add_ps(p0_i_f32x16) + _mm512_reduce_add_ps(p1_i_f32x16);
392
+
393
+ // Row i+1
394
+ __m512 p0_i1_f32x16 = nk_exp_ps_fast_avx512_(_mm512_sub_ps(s_scaled_f32x16[i + 1][0], max_i1_f32x16));
395
+ __m512 p1_i1_f32x16 = nk_exp_ps_fast_avx512_(_mm512_sub_ps(s_scaled_f32x16[i + 1][1], max_i1_f32x16));
396
+ _mm512_store_ps(weights_out + (i + 1) * 32 + 0, p0_i1_f32x16);
397
+ _mm512_store_ps(weights_out + (i + 1) * 32 + 16, p1_i1_f32x16);
398
+ row_sums[i + 1] = _mm512_reduce_add_ps(p0_i1_f32x16) + _mm512_reduce_add_ps(p1_i1_f32x16);
399
+ }
400
+
401
+ new_sum_f32x16 = _mm512_add_ps(new_sum_f32x16, _mm512_load_ps(row_sums));
402
+
403
+ state->row_max_f32x16 = new_max_f32x16;
404
+ state->row_sum_f32x16 = new_sum_f32x16;
405
+ }
406
+
407
+ /**
408
+ * @brief Initialize online softmax state.
409
+ */
410
+ NK_INTERNAL void nk_attention_softmax_init_(nk_attention_softmax_row_state_t *state) {
411
+ state->row_max_f32x16 = _mm512_set1_ps(NK_F32_MIN);
412
+ state->row_sum_f32x16 = _mm512_setzero_ps();
413
+ }
414
+
415
+ /**
416
+ * @brief Update softmax state with new score block and compute attention weights.
417
+ *
418
+ * For a 16×16 score block S[16][16]:
419
+ * 1. Compute row-wise max of S
420
+ * 2. Update running max: newₘₐₓ = max(oldₘₐₓ, blockₘₐₓ)
421
+ * 3. Rescale old sum: oldₛᵤₘ × = exp(oldₘₐₓ - newₘₐₓ)
422
+ * 4. Compute P = exp(S - newₘₐₓ), store for P × V
423
+ * 5. Update sum: newₛᵤₘ = oldₛᵤₘ + row_sum(P)
424
+ *
425
+ * @param state Running softmax state (updated in place)
426
+ * @param scores 16×16 score block in row-major order (256 floats)
427
+ * @param scale Scaling factor (1/√head_dim)
428
+ * @param weights_out Output: 16×16 attention weights P (pre-softmax normalized)
429
+ */
430
+ NK_INTERNAL void nk_attention_softmax_update_(nk_attention_softmax_row_state_t *state, nk_f32_t const *scores,
431
+ nk_f32_t scale, nk_f32_t *weights_out) {
432
+
433
+ __m512 scale_v_f32x16 = _mm512_set1_ps(scale);
434
+
435
+ // Load scores into 16 ZMM registers (one per row)
436
+ __m512 s_f32x16[16];
437
+ for (int i = 0; i < 16; i++) { s_f32x16[i] = _mm512_mul_ps(_mm512_load_ps(scores + i * 16), scale_v_f32x16); }
438
+
439
+ // Per-row max (each row has 16 elements, we need max across those 16)
440
+ // _mm512_reduce_max_ps returns a float scalar
441
+ NK_ALIGN64 float row_maxes[16];
442
+ for (int i = 0; i < 16; i++) { row_maxes[i] = _mm512_reduce_max_ps(s_f32x16[i]); }
443
+ __m512 row_max_new_f32x16 = _mm512_load_ps(row_maxes);
444
+
445
+ // Update running max
446
+ __m512 old_max_f32x16 = state->row_max_f32x16;
447
+ __m512 new_max_f32x16 = _mm512_max_ps(old_max_f32x16, row_max_new_f32x16);
448
+
449
+ // Rescale old sum: l = l × exp(oldₘₐₓ - newₘₐₓ)
450
+ __m512 correction_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(old_max_f32x16, new_max_f32x16));
451
+ __m512 old_sum_rescaled_f32x16 = _mm512_mul_ps(state->row_sum_f32x16, correction_f32x16);
452
+
453
+ // Compute P = exp(S - newₘₐₓ) for each row, accumulate sum
454
+ __m512 new_sum_f32x16 = old_sum_rescaled_f32x16;
455
+ nk_f32_t new_max_arr[16];
456
+ _mm512_store_ps(new_max_arr, new_max_f32x16);
457
+
458
+ for (int i = 0; i < 16; i++) {
459
+ __m512 max_broadcast_f32x16 = _mm512_set1_ps(new_max_arr[i]);
460
+ __m512 p_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(s_f32x16[i], max_broadcast_f32x16));
461
+ _mm512_store_ps(weights_out + i * 16, p_f32x16);
462
+
463
+ // Add row sum to running sum (at position i)
464
+ nk_f32_t row_sum = _mm512_reduce_add_ps(p_f32x16);
465
+ new_sum_f32x16 = _mm512_mask_add_ps(new_sum_f32x16, 1u << i, new_sum_f32x16, _mm512_set1_ps(row_sum));
466
+ }
467
+
468
+ state->row_max_f32x16 = new_max_f32x16;
469
+ state->row_sum_f32x16 = new_sum_f32x16;
470
+ }
471
+
472
+ /**
473
+ * @brief Rescale output accumulator when max changes.
474
+ *
475
+ * When processing a new KV block with larger scores, previous O accumulator
476
+ * needs rescaling: O = O × exp(oldₘₐₓ - newₘₐₓ)
477
+ *
478
+ * @param output Output accumulator [16][head_dim] in F32
479
+ * @param head_dim Head dimension
480
+ * @param old_max Previous running max per row (16 values)
481
+ * @param new_max New running max per row (16 values)
482
+ */
483
+ NK_INTERNAL void nk_attention_rescale_output_(nk_f32_t *output, nk_size_t head_dim, __m512 old_max_f32x16,
484
+ __m512 new_max_f32x16) {
485
+
486
+ __m512 correction_f32x16 = nk_exp_ps_avx512_(_mm512_sub_ps(old_max_f32x16, new_max_f32x16));
487
+ nk_f32_t corr_arr[16];
488
+ _mm512_store_ps(corr_arr, correction_f32x16);
489
+
490
+ for (nk_size_t row = 0; row < 16; row++) {
491
+ __m512 corr_v_f32x16 = _mm512_set1_ps(corr_arr[row]);
492
+ for (nk_size_t col = 0; col < head_dim; col += 16) {
493
+ __m512 o_f32x16 = _mm512_load_ps(output + row * head_dim + col);
494
+ o_f32x16 = _mm512_mul_ps(o_f32x16, corr_v_f32x16);
495
+ _mm512_store_ps(output + row * head_dim + col, o_f32x16);
496
+ }
497
+ }
498
+ }
499
+
500
+ NK_PUBLIC nk_size_t nk_attention_kv_packed_size_sapphireamx(nk_size_t num_kv_heads, nk_size_t head_dim,
501
+ nk_size_t max_seq_len) {
502
+
503
+ // Pad head_dim to multiple of 32 for AMX tiles
504
+ nk_size_t head_dim_padded = nk_size_round_up_to_multiple_(head_dim, 32);
505
+
506
+ // Each head: seq_len × head_dim_padded BF16 values
507
+ // Packed in AMX tile format: 16-row tiles with pair-interleaving
508
+ nk_size_t tiles_per_head_col = nk_size_divide_round_up_(max_seq_len, 16);
509
+ nk_size_t tiles_per_head_depth = head_dim_padded / 32;
510
+ nk_size_t bytes_per_head = tiles_per_head_col * tiles_per_head_depth * 1024; // 1KB per tile
511
+
512
+ // K and V each have num_kv_heads heads
513
+ nk_size_t k_size = num_kv_heads * bytes_per_head;
514
+ nk_size_t v_size = num_kv_heads * bytes_per_head;
515
+
516
+ // Header + K + V, all 64-byte aligned
517
+ return sizeof(nk_attention_kv_packed_header_t) + k_size + v_size;
518
+ }
519
+
520
+ NK_PUBLIC void nk_attention_pack_k_sapphireamx(nk_bf16_t const *k, void *kv_packed, nk_size_t num_kv_heads,
521
+ nk_size_t seq_len, nk_size_t head_dim) {
522
+
523
+ nk_attention_kv_packed_header_t *header = (nk_attention_kv_packed_header_t *)kv_packed;
524
+
525
+ // Initialize header
526
+ nk_size_t head_dim_padded = nk_size_round_up_to_multiple_(head_dim, 32);
527
+ header->num_kv_heads = (nk_u32_t)num_kv_heads;
528
+ header->head_dim = (nk_u32_t)head_dim;
529
+ header->head_dim_padded = (nk_u32_t)head_dim_padded;
530
+ header->seq_len = (nk_u32_t)seq_len;
531
+ header->k_offset = sizeof(nk_attention_kv_packed_header_t);
532
+
533
+ nk_bf16_t *k_packed = (nk_bf16_t *)((char *)kv_packed + header->k_offset);
534
+
535
+ // For Q × Kᵀ, K acts as B matrix but transposed
536
+ // K[h, s, d] → Kᵀ[h, d, s]
537
+ // Pack Kᵀ into AMX B tile format with pair-interleaving
538
+
539
+ nk_size_t tiles_per_seq = nk_size_divide_round_up_(seq_len, 16);
540
+ nk_size_t tiles_per_depth = head_dim_padded / 32;
541
+ nk_size_t tile_size = 512; // BF16 elements per tile
542
+
543
+ for (nk_size_t h = 0; h < num_kv_heads; h++) {
544
+ nk_bf16_t const *k_head = k + h * seq_len * head_dim;
545
+ nk_bf16_t *k_head_packed = k_packed + h * tiles_per_seq * tiles_per_depth * tile_size;
546
+
547
+ // Pack tiles: iterate over seq_len tiles (columns of Kᵀ) and depth tiles
548
+ for (nk_size_t seq_tile = 0; seq_tile < tiles_per_seq; seq_tile++) {
549
+ nk_size_t seq_start = seq_tile * 16;
550
+ nk_size_t valid_seq = (seq_start + 16 <= seq_len) ? 16 : (seq_len - seq_start);
551
+
552
+ for (nk_size_t depth_tile = 0; depth_tile < tiles_per_depth; depth_tile++) {
553
+ nk_size_t depth_start = depth_tile * 32;
554
+ nk_size_t valid_depth = (depth_start + 32 <= head_dim) ? 32 : (head_dim - depth_start);
555
+
556
+ // Tile index in packed format
557
+ nk_size_t tile_idx = seq_tile * tiles_per_depth + depth_tile;
558
+ nk_bf16_t *tile_ptr = k_head_packed + tile_idx * tile_size;
559
+
560
+ // Pack with pair-interleaving for TDPBF16PS
561
+ // B tile layout: data[depth/2][col][depth%2]
562
+ // For Kᵀ: depth is original head_dim, col is original seq position
563
+ for (nk_size_t d = 0; d < 32; d += 2) {
564
+ for (nk_size_t s = 0; s < 16; s++) {
565
+ nk_size_t dst_idx = (d / 2) * 32 + s * 2;
566
+
567
+ // K[h, seq_start + s, depth_start + d] and K[h, seq_start + s, depth_start + d + 1]
568
+ nk_bf16_t v0 = 0, v1 = 0;
569
+ if (s < valid_seq && d < valid_depth) {
570
+ v0 = k_head[(seq_start + s) * head_dim + depth_start + d];
571
+ }
572
+ if (s < valid_seq && d + 1 < valid_depth) {
573
+ v1 = k_head[(seq_start + s) * head_dim + depth_start + d + 1];
574
+ }
575
+
576
+ tile_ptr[dst_idx] = v0;
577
+ tile_ptr[dst_idx + 1] = v1;
578
+ }
579
+ }
580
+ }
581
+ }
582
+ }
583
+
584
+ // Calculate V offset
585
+ nk_size_t k_size = num_kv_heads * tiles_per_seq * tiles_per_depth * tile_size * sizeof(nk_bf16_t);
586
+ header->v_offset = header->k_offset + (nk_u32_t)k_size;
587
+ }
588
+
589
+ NK_PUBLIC void nk_attention_pack_v_sapphireamx(nk_bf16_t const *v, void *kv_packed, nk_size_t num_kv_heads,
590
+ nk_size_t seq_len, nk_size_t head_dim) {
591
+
592
+ nk_attention_kv_packed_header_t *header = (nk_attention_kv_packed_header_t *)kv_packed;
593
+ nk_size_t head_dim_padded = header->head_dim_padded;
594
+
595
+ nk_bf16_t *v_packed = (nk_bf16_t *)((char *)kv_packed + header->v_offset);
596
+
597
+ // For P @ V, P is [query_len, seq_len], V is [seq_len, head_dim]
598
+ // V acts as B matrix: pack with seq_len as "depth", head_dim as "columns"
599
+
600
+ nk_size_t tiles_per_seq = nk_size_divide_round_up_(seq_len, 32); // seq_len is depth for V
601
+ nk_size_t tiles_per_head = nk_size_divide_round_up_(head_dim_padded, 16); // head_dim is columns
602
+ nk_size_t tile_size = 512;
603
+
604
+ for (nk_size_t h = 0; h < num_kv_heads; h++) {
605
+ nk_bf16_t const *v_head = v + h * seq_len * head_dim;
606
+ nk_bf16_t *v_head_packed = v_packed + h * tiles_per_seq * tiles_per_head * tile_size;
607
+
608
+ for (nk_size_t seq_tile = 0; seq_tile < tiles_per_seq; seq_tile++) {
609
+ nk_size_t seq_start = seq_tile * 32;
610
+ nk_size_t valid_seq = (seq_start + 32 <= seq_len) ? 32 : (seq_len - seq_start);
611
+
612
+ for (nk_size_t head_tile = 0; head_tile < tiles_per_head; head_tile++) {
613
+ nk_size_t head_start = head_tile * 16;
614
+ nk_size_t valid_head = (head_start + 16 <= head_dim) ? 16 : (head_dim - head_start);
615
+
616
+ nk_size_t tile_idx = seq_tile * tiles_per_head + head_tile;
617
+ nk_bf16_t *tile_ptr = v_head_packed + tile_idx * tile_size;
618
+
619
+ // Pack with pair-interleaving
620
+ // B tile: data[depth/2][col][depth%2] where depth=seq, col=head_dim
621
+ for (nk_size_t s = 0; s < 32; s += 2) {
622
+ for (nk_size_t d = 0; d < 16; d++) {
623
+ nk_size_t dst_idx = (s / 2) * 32 + d * 2;
624
+
625
+ nk_bf16_t v0 = 0, v1 = 0;
626
+ if (s < valid_seq && d < valid_head) {
627
+ v0 = v_head[(seq_start + s) * head_dim + head_start + d];
628
+ }
629
+ if (s + 1 < valid_seq && d < valid_head) {
630
+ v1 = v_head[(seq_start + s + 1) * head_dim + head_start + d];
631
+ }
632
+
633
+ tile_ptr[dst_idx] = v0;
634
+ tile_ptr[dst_idx + 1] = v1;
635
+ }
636
+ }
637
+ }
638
+ }
639
+ }
640
+ }
641
+
642
+ /**
643
+ * @brief Extract K block from packed format: Kᵀ[head_dim, Bᶜ] for a given kv_block.
644
+ *
645
+ * K is packed as Kᵀ for Q × Kᵀ, with pair-interleaving.
646
+ * Output is in row-major F32 format: k_out[d × Bᶜ + kᵢ] = Kᵀ[d, kᵢ]
647
+ */
648
+ NK_INTERNAL void nk_attention_extract_k_block_(nk_bf16_t const *k_packed, nk_f32_t *k_out, nk_size_t kv_h,
649
+ nk_size_t kv_block_start, nk_size_t valid_kv, nk_size_t head_dim,
650
+ nk_size_t kv_len) {
651
+
652
+ nk_size_t const Bc = 16;
653
+ nk_size_t head_dim_padded = nk_size_round_up_to_multiple_(head_dim, 32);
654
+ nk_size_t tiles_per_seq = nk_size_divide_round_up_(kv_len, 16);
655
+ nk_size_t tiles_per_depth = head_dim_padded / 32;
656
+ nk_size_t tile_size = 512;
657
+
658
+ nk_size_t seq_tile = kv_block_start / 16;
659
+ nk_size_t base_s = kv_block_start % 16;
660
+
661
+ // Get pointer to this head's K data
662
+ nk_bf16_t const *k_head = k_packed + kv_h * tiles_per_seq * tiles_per_depth * tile_size;
663
+
664
+ // Extract each depth tile
665
+ for (nk_size_t depth_tile = 0; depth_tile < tiles_per_depth; depth_tile++) {
666
+ nk_size_t depth_start = depth_tile * 32;
667
+ nk_size_t tile_idx = seq_tile * tiles_per_depth + depth_tile;
668
+ nk_bf16_t const *tile_ptr = k_head + tile_idx * tile_size;
669
+
670
+ // Unpack tile: pair-interleaved layout data[d/2][s][d%2]
671
+ for (nk_size_t d_in_tile = 0; d_in_tile < 32 && depth_start + d_in_tile < head_dim; d_in_tile++) {
672
+ nk_size_t d = depth_start + d_in_tile;
673
+ for (nk_size_t ki = 0; ki < valid_kv; ki++) {
674
+ nk_size_t s_in_tile = base_s + ki;
675
+ if (s_in_tile >= 16) continue; // Shouldn't happen if kv_block aligned
676
+
677
+ nk_size_t elem_idx = (d_in_tile / 2) * 32 + s_in_tile * 2 + (d_in_tile % 2);
678
+ nk_bf16_t bf16_val = tile_ptr[elem_idx];
679
+ nk_f32_t f32_val;
680
+ nk_bf16_to_f32_serial(&bf16_val, &f32_val);
681
+ k_out[d * Bc + ki] = f32_val;
682
+ }
683
+ }
684
+ }
685
+ }
686
+
687
+ /**
688
+ * @brief Extract V block from packed format: V[Bᶜ, head_dim] for a given kv_block.
689
+ *
690
+ * V is packed for P × V, with pair-interleaving.
691
+ * Output is in row-major F32 format: v_out[kᵢ × head_dim + d] = V[kᵢ, d]
692
+ */
693
+ NK_INTERNAL void nk_attention_extract_v_block_(nk_bf16_t const *v_packed, nk_f32_t *v_out, nk_size_t kv_h,
694
+ nk_size_t kv_block_start, nk_size_t valid_kv, nk_size_t head_dim,
695
+ nk_size_t kv_len) {
696
+
697
+ nk_size_t head_dim_padded = nk_size_round_up_to_multiple_(head_dim, 32);
698
+ nk_size_t tiles_per_seq = nk_size_divide_round_up_(kv_len, 32);
699
+ nk_size_t tiles_per_head = nk_size_divide_round_up_(head_dim_padded, 16);
700
+ nk_size_t tile_size = 512;
701
+
702
+ // Get pointer to this head's V data
703
+ nk_bf16_t const *v_head = v_packed + kv_h * tiles_per_seq * tiles_per_head * tile_size;
704
+
705
+ // For each kv position in the block
706
+ for (nk_size_t ki = 0; ki < valid_kv; ki++) {
707
+ nk_size_t kv_pos = kv_block_start + ki;
708
+ nk_size_t seq_tile = kv_pos / 32;
709
+ nk_size_t s_in_tile = kv_pos % 32;
710
+
711
+ // Extract each head_dim tile
712
+ for (nk_size_t head_tile = 0; head_tile < tiles_per_head; head_tile++) {
713
+ nk_size_t head_start = head_tile * 16;
714
+ nk_size_t tile_idx = seq_tile * tiles_per_head + head_tile;
715
+ nk_bf16_t const *tile_ptr = v_head + tile_idx * tile_size;
716
+
717
+ // Unpack: pair-interleaved layout data[s/2][d][s%2]
718
+ for (nk_size_t d_in_tile = 0; d_in_tile < 16 && head_start + d_in_tile < head_dim; d_in_tile++) {
719
+ nk_size_t d = head_start + d_in_tile;
720
+ nk_size_t elem_idx = (s_in_tile / 2) * 32 + d_in_tile * 2 + (s_in_tile % 2);
721
+ nk_bf16_t bf16_val = tile_ptr[elem_idx];
722
+ nk_f32_t f32_val;
723
+ nk_bf16_to_f32_serial(&bf16_val, &f32_val);
724
+ v_out[ki * head_dim + d] = f32_val;
725
+ }
726
+ }
727
+ }
728
+ }
729
+
730
+ NK_PUBLIC void nk_attention_bf16_sapphireamx(nk_bf16_t const *q, void const *kv_packed, nk_f32_t *output,
731
+ nk_size_t num_heads, nk_size_t num_kv_heads, nk_size_t query_len,
732
+ nk_size_t kv_len, nk_size_t head_dim, nk_f32_t scale) {
733
+
734
+ nk_attention_kv_packed_header_t const *header = (nk_attention_kv_packed_header_t const *)kv_packed;
735
+ nk_size_t head_dim_padded = header->head_dim_padded;
736
+ nk_size_t gqa_ratio = num_heads / num_kv_heads;
737
+
738
+ // Tile sizes
739
+ nk_size_t const Br = 16; // Query block rows
740
+ nk_size_t const Bc = 16; // KV block columns
741
+
742
+ // Configure AMX tiles
743
+ nk_amx_tile_configure_sapphireamx_();
744
+
745
+ // Temporary buffers (aligned to 64 bytes)
746
+ NK_ALIGN64 nk_f32_t scores[16 * 16]; // S = Q × Kᵀ block
747
+ NK_ALIGN64 nk_f32_t weights[16 * 16]; // P = softmax(S)
748
+ NK_ALIGN64 nk_f32_t o_acc[16 * 256]; // Output accumulator (max head_dim=256)
749
+
750
+ // Packed data pointers
751
+ nk_bf16_t const *k_packed = (nk_bf16_t const *)((char const *)kv_packed + header->k_offset);
752
+ nk_bf16_t const *v_packed = (nk_bf16_t const *)((char const *)kv_packed + header->v_offset);
753
+
754
+ // Process each head
755
+ for (nk_size_t h = 0; h < num_heads; h++) {
756
+ nk_size_t kv_h = h / gqa_ratio;
757
+
758
+ nk_bf16_t const *q_head = q + h * query_len * head_dim;
759
+ nk_f32_t *o_head = output + h * query_len * head_dim;
760
+
761
+ // Process query blocks
762
+ for (nk_size_t qb = 0; qb < query_len; qb += Br) {
763
+ nk_size_t valid_q = (qb + Br <= query_len) ? Br : (query_len - qb);
764
+
765
+ // Initialize softmax state and output accumulator
766
+ nk_attention_softmax_row_state_t softmax_state;
767
+ nk_attention_softmax_init_(&softmax_state);
768
+
769
+ for (nk_size_t i = 0; i < valid_q * head_dim_padded; i++) { o_acc[i] = 0.0f; }
770
+
771
+ // Temporary buffers for extracted K and V blocks
772
+ NK_ALIGN64 nk_f32_t k_block[16 * 256]; // Kᵀ block [head_dim, 16]
773
+ NK_ALIGN64 nk_f32_t v_block[16 * 256]; // V block [16, head_dim]
774
+ NK_ALIGN64 nk_f32_t q_block[16 * 256]; // Q block [16, head_dim]
775
+
776
+ // Pre-convert Q block to F32
777
+ for (nk_size_t qi = 0; qi < valid_q; qi++) {
778
+ for (nk_size_t d = 0; d < head_dim; d++) {
779
+ nk_bf16_t q_val = q_head[(qb + qi) * head_dim + d];
780
+ nk_bf16_to_f32_serial(&q_val, &q_block[qi * head_dim + d]);
781
+ }
782
+ }
783
+
784
+ // Process KV blocks
785
+ for (nk_size_t kvb = 0; kvb < kv_len; kvb += Bc) {
786
+ nk_size_t valid_kv = (kvb + Bc <= kv_len) ? Bc : (kv_len - kvb);
787
+
788
+ // Extract K block: Kᵀ[head_dim, valid_kv] using bulk extraction
789
+ nk_attention_extract_k_block_(k_packed, k_block, kv_h, kvb, valid_kv, head_dim, kv_len);
790
+
791
+ // Phase 1: Compute S = Q × Kᵀ using AVX-512 FMA
792
+ for (nk_size_t qi = 0; qi < valid_q; qi++) {
793
+ for (nk_size_t ki = 0; ki < valid_kv; ki++) {
794
+ __m512 sum_v_f32x16 = _mm512_setzero_ps();
795
+ nk_size_t d = 0;
796
+ // Vectorized loop over head_dim
797
+ for (; d + 16 <= head_dim; d += 16) {
798
+ __m512 q_v_f32x16 = _mm512_loadu_ps(&q_block[qi * head_dim + d]);
799
+ // Kᵀ is stored as [head_dim, kv], gather is slow, use scalar for now
800
+ __m512 k_v_f32x16 = _mm512_set_ps(
801
+ k_block[(d + 15) * 16 + ki], k_block[(d + 14) * 16 + ki], k_block[(d + 13) * 16 + ki],
802
+ k_block[(d + 12) * 16 + ki], k_block[(d + 11) * 16 + ki], k_block[(d + 10) * 16 + ki],
803
+ k_block[(d + 9) * 16 + ki], k_block[(d + 8) * 16 + ki], k_block[(d + 7) * 16 + ki],
804
+ k_block[(d + 6) * 16 + ki], k_block[(d + 5) * 16 + ki], k_block[(d + 4) * 16 + ki],
805
+ k_block[(d + 3) * 16 + ki], k_block[(d + 2) * 16 + ki], k_block[(d + 1) * 16 + ki],
806
+ k_block[(d + 0) * 16 + ki]);
807
+ sum_v_f32x16 = _mm512_fmadd_ps(q_v_f32x16, k_v_f32x16, sum_v_f32x16);
808
+ }
809
+ nk_f32_t sum = _mm512_reduce_add_ps(sum_v_f32x16);
810
+ // Scalar tail
811
+ for (; d < head_dim; d++) { sum += q_block[qi * head_dim + d] * k_block[d * 16 + ki]; }
812
+ scores[qi * 16 + ki] = sum;
813
+ }
814
+ // Zero out invalid KV positions
815
+ for (nk_size_t ki = valid_kv; ki < 16; ki++) { scores[qi * 16 + ki] = NK_F32_MIN; }
816
+ }
817
+ // Zero out invalid query rows
818
+ for (nk_size_t qi = valid_q; qi < 16; qi++) {
819
+ for (nk_size_t ki = 0; ki < 16; ki++) { scores[qi * 16 + ki] = NK_F32_MIN; }
820
+ }
821
+
822
+ // Phase 2: Online softmax update
823
+ __m512 old_max_f32x16 = softmax_state.row_max_f32x16;
824
+ nk_attention_softmax_update_(&softmax_state, scores, scale, weights);
825
+
826
+ // Rescale output accumulator if max changed
827
+ nk_attention_rescale_output_(o_acc, head_dim_padded, old_max_f32x16, softmax_state.row_max_f32x16);
828
+
829
+ // Extract V block: V[valid_kv, head_dim] using bulk extraction
830
+ nk_attention_extract_v_block_(v_packed, v_block, kv_h, kvb, valid_kv, head_dim, kv_len);
831
+
832
+ // Phase 3: Compute O += P × V using AVX-512 FMA
833
+ for (nk_size_t qi = 0; qi < valid_q; qi++) {
834
+ nk_size_t d = 0;
835
+ // Vectorized loop over head_dim
836
+ for (; d + 16 <= head_dim; d += 16) {
837
+ __m512 acc_v_f32x16 = _mm512_loadu_ps(&o_acc[qi * head_dim_padded + d]);
838
+ for (nk_size_t ki = 0; ki < valid_kv; ki++) {
839
+ __m512 p_v_f32x16 = _mm512_set1_ps(weights[qi * 16 + ki]);
840
+ __m512 v_v_f32x16 = _mm512_loadu_ps(&v_block[ki * head_dim + d]);
841
+ acc_v_f32x16 = _mm512_fmadd_ps(p_v_f32x16, v_v_f32x16, acc_v_f32x16);
842
+ }
843
+ _mm512_storeu_ps(&o_acc[qi * head_dim_padded + d], acc_v_f32x16);
844
+ }
845
+ // Scalar tail
846
+ for (; d < head_dim; d++) {
847
+ nk_f32_t sum = o_acc[qi * head_dim_padded + d];
848
+ for (nk_size_t ki = 0; ki < valid_kv; ki++) {
849
+ sum += weights[qi * 16 + ki] * v_block[ki * head_dim + d];
850
+ }
851
+ o_acc[qi * head_dim_padded + d] = sum;
852
+ }
853
+ }
854
+ }
855
+
856
+ // Finalize: normalize O by row sums
857
+ nk_f32_t row_sums[16];
858
+ _mm512_store_ps(row_sums, softmax_state.row_sum_f32x16);
859
+
860
+ for (nk_size_t qi = 0; qi < valid_q; qi++) {
861
+ nk_f32_t inv_sum = 1.0f / row_sums[qi];
862
+ for (nk_size_t d = 0; d < head_dim; d++) {
863
+ o_head[(qb + qi) * head_dim + d] = o_acc[qi * head_dim_padded + d] * inv_sum;
864
+ }
865
+ }
866
+ }
867
+ }
868
+ }
869
+
870
+ NK_PUBLIC void nk_attention_bf16_amx_bc32_sapphireamx(nk_bf16_t const *q, void const *kv_packed, nk_f32_t *output,
871
+ nk_size_t num_heads, nk_size_t num_kv_heads, nk_size_t query_len,
872
+ nk_size_t kv_len, nk_size_t head_dim, nk_f32_t scale) {
873
+
874
+ nk_attention_kv_packed_header_t const *header = (nk_attention_kv_packed_header_t const *)kv_packed;
875
+ nk_size_t head_dim_padded = header->head_dim_padded;
876
+ nk_size_t gqa_ratio = num_heads / num_kv_heads;
877
+
878
+ // Block sizes - Bc=32 matches V tile depth granularity
879
+ nk_size_t const Br = 16;
880
+ nk_size_t const Bc = 32;
881
+
882
+ // Configure AMX tiles
883
+ nk_amx_tile_configure_sapphireamx_();
884
+
885
+ // Buffers
886
+ NK_ALIGN64 nk_f32_t scores[16 * 32]; // S [16, 32]
887
+ NK_ALIGN64 nk_f32_t weights[16 * 32]; // P [16, 32]
888
+ NK_ALIGN64 nk_f32_t o_acc[16 * 256]; // Output accumulator
889
+ NK_ALIGN64 nk_bf16_t q_tile[16][32]; // Q as A-tile format
890
+ NK_ALIGN64 nk_f32_t s_tile[16][16]; // Score tile output (for each half)
891
+ NK_ALIGN64 nk_bf16_t p_tile[16][32]; // P weights as A-tile format
892
+ NK_ALIGN64 nk_f32_t o_tile[16][16]; // Output tile from AMX
893
+
894
+ // K packing layout (16 seq positions per tile)
895
+ nk_size_t k_tiles_per_seq = nk_size_divide_round_up_(kv_len, 16);
896
+ nk_size_t tiles_per_depth = head_dim_padded / 32;
897
+ nk_size_t tile_size = 512; // BF16 elements per tile
898
+
899
+ // V packing layout (32 seq positions per tile)
900
+ nk_size_t v_tiles_per_seq = nk_size_divide_round_up_(kv_len, 32);
901
+ nk_size_t v_tiles_per_head = nk_size_divide_round_up_(head_dim_padded, 16);
902
+
903
+ nk_bf16_t const *k_packed = (nk_bf16_t const *)((char const *)kv_packed + header->k_offset);
904
+ nk_bf16_t const *v_packed = (nk_bf16_t const *)((char const *)kv_packed + header->v_offset);
905
+
906
+ for (nk_size_t h = 0; h < num_heads; h++) {
907
+ nk_size_t kv_h = h / gqa_ratio;
908
+ nk_bf16_t const *q_head = q + h * query_len * head_dim;
909
+ nk_f32_t *o_head = output + h * query_len * head_dim;
910
+
911
+ // Pointer to this KV head's packed data
912
+ nk_bf16_t const *k_head = k_packed + kv_h * k_tiles_per_seq * tiles_per_depth * tile_size;
913
+ nk_bf16_t const *v_head = v_packed + kv_h * v_tiles_per_seq * v_tiles_per_head * tile_size;
914
+
915
+ for (nk_size_t qb = 0; qb < query_len; qb += Br) {
916
+ nk_size_t valid_q = (qb + Br <= query_len) ? Br : (query_len - qb);
917
+
918
+ nk_attention_softmax_row_state_t softmax_state;
919
+ nk_attention_softmax_init_(&softmax_state);
920
+
921
+ // Zero output accumulator using SIMD
922
+ __m512 zero_f32x16 = _mm512_setzero_ps();
923
+ for (nk_size_t i = 0; i < 16 * head_dim_padded; i += 64) {
924
+ _mm512_store_ps(&o_acc[i], zero_f32x16);
925
+ _mm512_store_ps(&o_acc[i + 16], zero_f32x16);
926
+ _mm512_store_ps(&o_acc[i + 32], zero_f32x16);
927
+ _mm512_store_ps(&o_acc[i + 48], zero_f32x16);
928
+ }
929
+
930
+ // Process KV blocks in chunks of 32
931
+ for (nk_size_t kvb = 0; kvb < kv_len; kvb += Bc) {
932
+ nk_size_t valid_kv = (kvb + Bc <= kv_len) ? Bc : (kv_len - kvb);
933
+
934
+ // Phase 1: S = Q × Kᵀ using AMX
935
+ // Need 2 K tiles per block (each K tile has 16 columns)
936
+ nk_size_t k_tile_idx0 = kvb / 16; // First K tile
937
+ nk_size_t k_tile_idx1 = (kvb + 16) / 16; // Second K tile
938
+
939
+ // Process first half: S[0:16, 0:16]
940
+ _tile_zero(0); // TMM0 = score accumulator for first 16 columns
941
+ _tile_zero(3); // TMM3 = score accumulator for second 16 columns
942
+
943
+ for (nk_size_t dt = 0; dt < tiles_per_depth; dt++) {
944
+ nk_size_t depth_start = dt * 32;
945
+
946
+ // Load Q[qb:qb+16, depth_start:depth_start+32] into A-tile format
947
+ // Use SIMD loads when possible (full 32 elements per row)
948
+ if (depth_start + 32 <= head_dim) {
949
+ // Full tile - use fast SIMD copy
950
+ for (nk_size_t row = 0; row < valid_q; row++) {
951
+ nk_bf16_t const *q_row = q_head + (qb + row) * head_dim + depth_start;
952
+ // Load 32 BF16 values (64 bytes) using two 256-bit loads
953
+ __m256i q0_bf16x16 = _mm256_loadu_si256((__m256i const *)q_row);
954
+ __m256i q1_bf16x16 = _mm256_loadu_si256((__m256i const *)(q_row + 16));
955
+ _mm256_store_si256((__m256i *)&q_tile[row][0], q0_bf16x16);
956
+ _mm256_store_si256((__m256i *)&q_tile[row][16], q1_bf16x16);
957
+ }
958
+ }
959
+ else {
960
+ // Partial tile - element-by-element with padding
961
+ nk_size_t valid_depth = head_dim - depth_start;
962
+ for (nk_size_t row = 0; row < valid_q; row++) {
963
+ nk_bf16_t const *q_row = q_head + (qb + row) * head_dim + depth_start;
964
+ for (nk_size_t col = 0; col < 32; col++) {
965
+ q_tile[row][col] = (col < valid_depth) ? q_row[col] : 0;
966
+ }
967
+ }
968
+ }
969
+ // Zero pad remaining rows
970
+ for (nk_size_t row = valid_q; row < 16; row++) {
971
+ _mm256_store_si256((__m256i *)&q_tile[row][0], _mm256_setzero_si256());
972
+ _mm256_store_si256((__m256i *)&q_tile[row][16], _mm256_setzero_si256());
973
+ }
974
+
975
+ _tile_loadd(1, q_tile, 64); // A: 16×32 BF16
976
+
977
+ // First K tile (columns 0:16)
978
+ nk_bf16_t const *k_tile_ptr0 = k_head + (k_tile_idx0 * tiles_per_depth + dt) * tile_size;
979
+ _tile_loadd(2, k_tile_ptr0, 64); // B: 32×16 BF16
980
+ _tile_dpbf16ps(0, 1, 2); // TMM0 += Q × K0
981
+
982
+ // Second K tile (columns 16:32) if within bounds
983
+ if (kvb + 16 < kv_len) {
984
+ nk_bf16_t const *k_tile_ptr1 = k_head + (k_tile_idx1 * tiles_per_depth + dt) * tile_size;
985
+ _tile_loadd(4, k_tile_ptr1, 64); // B: 32×16 BF16
986
+ _tile_dpbf16ps(3, 1, 4); // TMM3 += Q × K1
987
+ }
988
+ }
989
+
990
+ // Store scores from TMM0 and TMM3
991
+ // Use SIMD for fast extraction
992
+ _tile_stored(0, s_tile, 64);
993
+
994
+ __m512 neg_inf_f32x16 = _mm512_set1_ps(NK_F32_MIN);
995
+
996
+ if (valid_q == 16 && valid_kv >= 16) {
997
+ // Fast path: full first half, just copy
998
+ for (nk_size_t qi = 0; qi < 16; qi++) {
999
+ __m512 s0_f32x16 = _mm512_load_ps(&s_tile[qi][0]);
1000
+ _mm512_store_ps(&scores[qi * 32], s0_f32x16);
1001
+ }
1002
+ }
1003
+ else {
1004
+ // Partial - need masking
1005
+ __mmask16 kv_mask = (1u << valid_kv) - 1;
1006
+ for (nk_size_t qi = 0; qi < 16; qi++) {
1007
+ __m512 s0_f32x16 = _mm512_load_ps(&s_tile[qi][0]);
1008
+ if (qi < valid_q) { s0_f32x16 = _mm512_mask_blend_ps(kv_mask, neg_inf_f32x16, s0_f32x16); }
1009
+ else { s0_f32x16 = neg_inf_f32x16; }
1010
+ _mm512_store_ps(&scores[qi * 32], s0_f32x16);
1011
+ }
1012
+ }
1013
+
1014
+ // Second half scores (columns 16:32)
1015
+ if (kvb + 16 < kv_len) {
1016
+ _tile_stored(3, s_tile, 64);
1017
+ nk_size_t valid_kv2 = (valid_kv > 16) ? (valid_kv - 16) : 0;
1018
+
1019
+ if (valid_q == 16 && valid_kv2 >= 16) {
1020
+ // Fast path
1021
+ for (nk_size_t qi = 0; qi < 16; qi++) {
1022
+ __m512 s1_f32x16 = _mm512_load_ps(&s_tile[qi][0]);
1023
+ _mm512_store_ps(&scores[qi * 32 + 16], s1_f32x16);
1024
+ }
1025
+ }
1026
+ else {
1027
+ __mmask16 kv_mask2 = (valid_kv2 >= 16) ? 0xFFFF : ((1u << valid_kv2) - 1);
1028
+ for (nk_size_t qi = 0; qi < 16; qi++) {
1029
+ __m512 s1_f32x16 = _mm512_load_ps(&s_tile[qi][0]);
1030
+ if (qi < valid_q) { s1_f32x16 = _mm512_mask_blend_ps(kv_mask2, neg_inf_f32x16, s1_f32x16); }
1031
+ else { s1_f32x16 = neg_inf_f32x16; }
1032
+ _mm512_store_ps(&scores[qi * 32 + 16], s1_f32x16);
1033
+ }
1034
+ }
1035
+ }
1036
+ else {
1037
+ // Mask out second half entirely
1038
+ for (nk_size_t qi = 0; qi < 16; qi++) { _mm512_store_ps(&scores[qi * 32 + 16], neg_inf_f32x16); }
1039
+ }
1040
+
1041
+ // Phase 2: online softmax (fast degree-4 exp)
1042
+ __m512 old_max_f32x16 = softmax_state.row_max_f32x16;
1043
+ nk_attention_softmax_update_bc32_fast_(&softmax_state, scores, scale, weights);
1044
+ nk_attention_rescale_output_(o_acc, head_dim_padded, old_max_f32x16, softmax_state.row_max_f32x16);
1045
+
1046
+ // Phase 3: O += P × V using AMX
1047
+ // Convert P[16, 32] from F32 to BF16 and pack as A-tile
1048
+ for (nk_size_t qi = 0; qi < 16; qi++) {
1049
+ for (nk_size_t ki = 0; ki < 32; ki += 16) {
1050
+ __m512 p_f32_f32x16 = _mm512_loadu_ps(&weights[qi * 32 + ki]);
1051
+ __m256bh p_bf16 = _mm512_cvtneps_pbh(p_f32_f32x16);
1052
+ // Store BF16 vector - cast through union or memory
1053
+ *(__m256bh *)&p_tile[qi][ki] = p_bf16;
1054
+ }
1055
+ }
1056
+
1057
+ // V tile index for this block
1058
+ nk_size_t v_seq_tile = kvb / 32;
1059
+
1060
+ // For each head_dim chunk of 16
1061
+ for (nk_size_t ht = 0; ht < v_tiles_per_head; ht++) {
1062
+ nk_size_t head_start = ht * 16;
1063
+
1064
+ // V tile is already packed: V[32, 16] in B-tile format
1065
+ nk_bf16_t const *v_tile_ptr = v_head + (v_seq_tile * v_tiles_per_head + ht) * tile_size;
1066
+
1067
+ // Zero output tile
1068
+ _tile_zero(5);
1069
+
1070
+ // Load P into TMM6 (A-tile: 16×32)
1071
+ _tile_loadd(6, p_tile, 64);
1072
+
1073
+ // Load V into TMM7 (B-tile: 32×16)
1074
+ _tile_loadd(7, v_tile_ptr, 64);
1075
+
1076
+ // O_tile = P × V
1077
+ _tile_dpbf16ps(5, 6, 7);
1078
+
1079
+ // Store and accumulate
1080
+ _tile_stored(5, o_tile, 64);
1081
+
1082
+ // Add to output accumulator - unrolled for all 16 rows
1083
+ // Even if valid_q < 16, we accumulate all (padded rows have zero_f32x16 weights)
1084
+ for (nk_size_t qi = 0; qi < 16; qi += 4) {
1085
+ __m512 acc0_f32x16 = _mm512_load_ps(&o_acc[(qi + 0) * head_dim_padded + head_start]);
1086
+ __m512 acc1_f32x16 = _mm512_load_ps(&o_acc[(qi + 1) * head_dim_padded + head_start]);
1087
+ __m512 acc2_f32x16 = _mm512_load_ps(&o_acc[(qi + 2) * head_dim_padded + head_start]);
1088
+ __m512 acc3_f32x16 = _mm512_load_ps(&o_acc[(qi + 3) * head_dim_padded + head_start]);
1089
+
1090
+ acc0_f32x16 = _mm512_add_ps(acc0_f32x16, _mm512_load_ps(&o_tile[qi + 0][0]));
1091
+ acc1_f32x16 = _mm512_add_ps(acc1_f32x16, _mm512_load_ps(&o_tile[qi + 1][0]));
1092
+ acc2_f32x16 = _mm512_add_ps(acc2_f32x16, _mm512_load_ps(&o_tile[qi + 2][0]));
1093
+ acc3_f32x16 = _mm512_add_ps(acc3_f32x16, _mm512_load_ps(&o_tile[qi + 3][0]));
1094
+
1095
+ _mm512_store_ps(&o_acc[(qi + 0) * head_dim_padded + head_start], acc0_f32x16);
1096
+ _mm512_store_ps(&o_acc[(qi + 1) * head_dim_padded + head_start], acc1_f32x16);
1097
+ _mm512_store_ps(&o_acc[(qi + 2) * head_dim_padded + head_start], acc2_f32x16);
1098
+ _mm512_store_ps(&o_acc[(qi + 3) * head_dim_padded + head_start], acc3_f32x16);
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ // Finalize: normalize O by row sums
1104
+ nk_f32_t row_sums[16];
1105
+ _mm512_store_ps(row_sums, softmax_state.row_sum_f32x16);
1106
+ for (nk_size_t qi = 0; qi < valid_q; qi++) {
1107
+ nk_f32_t inv_sum = 1.0f / row_sums[qi];
1108
+ for (nk_size_t d = 0; d < head_dim; d++) {
1109
+ o_head[(qb + qi) * head_dim + d] = o_acc[qi * head_dim_padded + d] * inv_sum;
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ NK_PUBLIC void nk_attention_bf16_amx_optimized_sapphireamx(nk_bf16_t const *q, void const *kv_packed, nk_f32_t *output,
1117
+ nk_size_t num_heads, nk_size_t num_kv_heads,
1118
+ nk_size_t query_len, nk_size_t kv_len, nk_size_t head_dim,
1119
+ nk_f32_t scale) {
1120
+
1121
+ nk_attention_kv_packed_header_t const *header = (nk_attention_kv_packed_header_t const *)kv_packed;
1122
+ nk_size_t head_dim_padded = header->head_dim_padded;
1123
+ nk_size_t gqa_ratio = num_heads / num_kv_heads;
1124
+
1125
+ nk_size_t const Br = 16;
1126
+ nk_size_t const Bc = 32;
1127
+
1128
+ // Configure AMX tiles once
1129
+ nk_amx_tile_configure_sapphireamx_();
1130
+
1131
+ // Tile dimensions
1132
+ nk_size_t tiles_per_depth = head_dim_padded / 32; // 4 for d=128
1133
+ nk_size_t v_tiles_per_head = nk_size_divide_round_up_(head_dim_padded, 16); // 8 for d=128
1134
+
1135
+ // K packing layout (16 seq positions per tile)
1136
+ nk_size_t k_tiles_per_seq = nk_size_divide_round_up_(kv_len, 16);
1137
+ nk_size_t tile_size = 512;
1138
+
1139
+ // V packing layout (32 seq positions per tile)
1140
+ nk_size_t v_tiles_per_seq = nk_size_divide_round_up_(kv_len, 32);
1141
+
1142
+ nk_bf16_t const *k_packed = (nk_bf16_t const *)((char const *)kv_packed + header->k_offset);
1143
+ nk_bf16_t const *v_packed = (nk_bf16_t const *)((char const *)kv_packed + header->v_offset);
1144
+
1145
+ // Pre-allocated buffers (all L1-resident)
1146
+ NK_ALIGN64 nk_bf16_t q_tiles[4][16][32]; // Q tiles for all depth chunks (max 4 for d=128)
1147
+ NK_ALIGN64 nk_f32_t scores[16][32]; // Score buffer (direct tile store target)
1148
+ NK_ALIGN64 nk_f32_t weights[16][32]; // Softmax output
1149
+ NK_ALIGN64 nk_bf16_t p_tile[16][32]; // P weights in BF16
1150
+ NK_ALIGN64 nk_f32_t o_tile[16][16]; // Output tile buffer
1151
+ NK_ALIGN64 nk_f32_t o_acc[16][256]; // Output accumulator (max d=256)
1152
+
1153
+ __m512 neg_inf_f32x16 = _mm512_set1_ps(NK_F32_MIN);
1154
+
1155
+ for (nk_size_t h = 0; h < num_heads; h++) {
1156
+ nk_size_t kv_h = h / gqa_ratio;
1157
+ nk_bf16_t const *q_head = q + h * query_len * head_dim;
1158
+ nk_f32_t *o_head = output + h * query_len * head_dim;
1159
+
1160
+ nk_bf16_t const *k_head = k_packed + kv_h * k_tiles_per_seq * tiles_per_depth * tile_size;
1161
+ nk_bf16_t const *v_head = v_packed + kv_h * v_tiles_per_seq * v_tiles_per_head * tile_size;
1162
+
1163
+ for (nk_size_t qb = 0; qb < query_len; qb += Br) {
1164
+ nk_size_t valid_q = (qb + Br <= query_len) ? Br : (query_len - qb);
1165
+
1166
+ // Pre-pack Q tiles once for all KV blocks
1167
+ for (nk_size_t dt = 0; dt < tiles_per_depth; dt++) {
1168
+ nk_size_t depth_start = dt * 32;
1169
+ if (depth_start + 32 <= head_dim) {
1170
+ // Full tile - fast SIMD copy
1171
+ for (nk_size_t row = 0; row < valid_q; row++) {
1172
+ nk_bf16_t const *q_row = q_head + (qb + row) * head_dim + depth_start;
1173
+ __m256i q0_bf16x16 = _mm256_loadu_si256((__m256i const *)q_row);
1174
+ __m256i q1_bf16x16 = _mm256_loadu_si256((__m256i const *)(q_row + 16));
1175
+ _mm256_store_si256((__m256i *)&q_tiles[dt][row][0], q0_bf16x16);
1176
+ _mm256_store_si256((__m256i *)&q_tiles[dt][row][16], q1_bf16x16);
1177
+ }
1178
+ // Zero remaining rows
1179
+ for (nk_size_t row = valid_q; row < 16; row++) {
1180
+ _mm256_store_si256((__m256i *)&q_tiles[dt][row][0], _mm256_setzero_si256());
1181
+ _mm256_store_si256((__m256i *)&q_tiles[dt][row][16], _mm256_setzero_si256());
1182
+ }
1183
+ }
1184
+ else {
1185
+ // Partial tile with padding
1186
+ nk_size_t valid_depth = head_dim - depth_start;
1187
+ for (nk_size_t row = 0; row < 16; row++) {
1188
+ for (nk_size_t col = 0; col < 32; col++) {
1189
+ if (row < valid_q && col < valid_depth) {
1190
+ q_tiles[dt][row][col] = q_head[(qb + row) * head_dim + depth_start + col];
1191
+ }
1192
+ else { q_tiles[dt][row][col] = 0; }
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+
1198
+ // Initialize softmax state and output accumulator
1199
+ nk_attention_softmax_row_state_t softmax_state;
1200
+ nk_attention_softmax_init_(&softmax_state);
1201
+
1202
+ __m512 zero_f32x16 = _mm512_setzero_ps();
1203
+ for (nk_size_t i = 0; i < 16 * head_dim_padded; i += 64) {
1204
+ _mm512_store_ps(&o_acc[0][i], zero_f32x16);
1205
+ _mm512_store_ps(&o_acc[0][i + 16], zero_f32x16);
1206
+ _mm512_store_ps(&o_acc[0][i + 32], zero_f32x16);
1207
+ _mm512_store_ps(&o_acc[0][i + 48], zero_f32x16);
1208
+ }
1209
+
1210
+ // Process KV blocks
1211
+ for (nk_size_t kvb = 0; kvb < kv_len; kvb += Bc) {
1212
+ nk_size_t valid_kv = (kvb + Bc <= kv_len) ? Bc : (kv_len - kvb);
1213
+ nk_size_t k_tile_idx0 = kvb / 16;
1214
+ nk_size_t k_tile_idx1 = (kvb + 16) / 16;
1215
+
1216
+ // Phase 1: S = Q × Kᵀ using pre-packed Q tiles
1217
+ _tile_zero(0); // Score cols 0:16
1218
+ _tile_zero(3); // Score cols 16:32
1219
+
1220
+ for (nk_size_t dt = 0; dt < tiles_per_depth; dt++) {
1221
+ // Load pre-packed Q tile from L1 (not global!)
1222
+ _tile_loadd(1, q_tiles[dt], 64);
1223
+
1224
+ // Load K tiles from global (necessary)
1225
+ nk_bf16_t const *k_tile_ptr0 = k_head + (k_tile_idx0 * tiles_per_depth + dt) * tile_size;
1226
+ _tile_loadd(2, k_tile_ptr0, 64);
1227
+ _tile_dpbf16ps(0, 1, 2);
1228
+
1229
+ if (kvb + 16 < kv_len) {
1230
+ nk_bf16_t const *k_tile_ptr1 = k_head + (k_tile_idx1 * tiles_per_depth + dt) * tile_size;
1231
+ _tile_loadd(4, k_tile_ptr1, 64);
1232
+ _tile_dpbf16ps(3, 1, 4);
1233
+ }
1234
+ }
1235
+
1236
+ // Store first 16 columns directly to scores[0:16]
1237
+ _tile_stored(0, &scores[0][0], 128); // stride=128 bytes (32 floats)
1238
+
1239
+ // Store second 16 columns to scores[16:32]
1240
+ if (kvb + 16 < kv_len) { _tile_stored(3, &scores[0][16], 128); }
1241
+ else {
1242
+ // Mask out second half
1243
+ for (nk_size_t qi = 0; qi < 16; qi++) { _mm512_store_ps(&scores[qi][16], neg_inf_f32x16); }
1244
+ }
1245
+
1246
+ // Apply masking for invalid positions (only on boundaries)
1247
+ if (valid_q < 16 || valid_kv < 32) {
1248
+ __mmask16 kv_mask0 = (valid_kv >= 16) ? 0xFFFF : ((1u << valid_kv) - 1);
1249
+ __mmask16 kv_mask1 = (valid_kv > 16) ? ((1u << (valid_kv - 16)) - 1) : 0;
1250
+ if (valid_kv >= 32) kv_mask1 = 0xFFFF;
1251
+
1252
+ for (nk_size_t qi = 0; qi < 16; qi++) {
1253
+ if (qi >= valid_q) {
1254
+ _mm512_store_ps(&scores[qi][0], neg_inf_f32x16);
1255
+ _mm512_store_ps(&scores[qi][16], neg_inf_f32x16);
1256
+ }
1257
+ else {
1258
+ __m512 s0_f32x16 = _mm512_load_ps(&scores[qi][0]);
1259
+ __m512 s1_f32x16 = _mm512_load_ps(&scores[qi][16]);
1260
+ _mm512_store_ps(&scores[qi][0], _mm512_mask_blend_ps(kv_mask0, neg_inf_f32x16, s0_f32x16));
1261
+ _mm512_store_ps(&scores[qi][16], _mm512_mask_blend_ps(kv_mask1, neg_inf_f32x16, s1_f32x16));
1262
+ }
1263
+ }
1264
+ }
1265
+
1266
+ // Phase 2: online softmax (fast degree-4 exp)
1267
+ __m512 old_max_f32x16 = softmax_state.row_max_f32x16;
1268
+ nk_attention_softmax_update_bc32_fast_(&softmax_state, &scores[0][0], scale, &weights[0][0]);
1269
+ nk_attention_rescale_output_(&o_acc[0][0], head_dim_padded, old_max_f32x16,
1270
+ softmax_state.row_max_f32x16);
1271
+
1272
+ // Phase 3: O += P × V with hoisted P tile load
1273
+ // Convert F32 weights to BF16 P tile (once per KV block)
1274
+ for (nk_size_t qi = 0; qi < 16; qi++) {
1275
+ __m512 p0_f32x16 = _mm512_load_ps(&weights[qi][0]);
1276
+ __m512 p1_f32x16 = _mm512_load_ps(&weights[qi][16]);
1277
+ __m256bh pb0 = _mm512_cvtneps_pbh(p0_f32x16);
1278
+ __m256bh pb1 = _mm512_cvtneps_pbh(p1_f32x16);
1279
+ *(__m256bh *)&p_tile[qi][0] = pb0;
1280
+ *(__m256bh *)&p_tile[qi][16] = pb1;
1281
+ }
1282
+
1283
+ // Load P tile once, reuse for all V tiles
1284
+ _tile_loadd(6, p_tile, 64);
1285
+
1286
+ nk_size_t v_seq_tile = kvb / 32;
1287
+
1288
+ for (nk_size_t ht = 0; ht < v_tiles_per_head; ht++) {
1289
+ nk_size_t head_start = ht * 16;
1290
+
1291
+ // Load V tile from global
1292
+ nk_bf16_t const *v_tile_ptr = v_head + (v_seq_tile * v_tiles_per_head + ht) * tile_size;
1293
+
1294
+ _tile_zero(5);
1295
+ // P already in TMM6 - no reload!
1296
+ _tile_loadd(7, v_tile_ptr, 64);
1297
+ _tile_dpbf16ps(5, 6, 7);
1298
+
1299
+ // Store and accumulate
1300
+ _tile_stored(5, o_tile, 64);
1301
+
1302
+ // Accumulate into output (unrolled)
1303
+ for (nk_size_t qi = 0; qi < 16; qi += 4) {
1304
+ __m512 acc0_f32x16 = _mm512_load_ps(&o_acc[qi + 0][head_start]);
1305
+ __m512 acc1_f32x16 = _mm512_load_ps(&o_acc[qi + 1][head_start]);
1306
+ __m512 acc2_f32x16 = _mm512_load_ps(&o_acc[qi + 2][head_start]);
1307
+ __m512 acc3_f32x16 = _mm512_load_ps(&o_acc[qi + 3][head_start]);
1308
+
1309
+ acc0_f32x16 = _mm512_add_ps(acc0_f32x16, _mm512_load_ps(&o_tile[qi + 0][0]));
1310
+ acc1_f32x16 = _mm512_add_ps(acc1_f32x16, _mm512_load_ps(&o_tile[qi + 1][0]));
1311
+ acc2_f32x16 = _mm512_add_ps(acc2_f32x16, _mm512_load_ps(&o_tile[qi + 2][0]));
1312
+ acc3_f32x16 = _mm512_add_ps(acc3_f32x16, _mm512_load_ps(&o_tile[qi + 3][0]));
1313
+
1314
+ _mm512_store_ps(&o_acc[qi + 0][head_start], acc0_f32x16);
1315
+ _mm512_store_ps(&o_acc[qi + 1][head_start], acc1_f32x16);
1316
+ _mm512_store_ps(&o_acc[qi + 2][head_start], acc2_f32x16);
1317
+ _mm512_store_ps(&o_acc[qi + 3][head_start], acc3_f32x16);
1318
+ }
1319
+ }
1320
+ }
1321
+
1322
+ // Finalize: normalize O by row sums
1323
+ nk_f32_t row_sums[16];
1324
+ _mm512_store_ps(row_sums, softmax_state.row_sum_f32x16);
1325
+ for (nk_size_t qi = 0; qi < valid_q; qi++) {
1326
+ __m512 inv_sum_f32x16 = _mm512_set1_ps(1.0f / row_sums[qi]);
1327
+ for (nk_size_t d = 0; d < head_dim; d += 16) {
1328
+ __m512 o_f32x16 = _mm512_load_ps(&o_acc[qi][d]);
1329
+ o_f32x16 = _mm512_mul_ps(o_f32x16, inv_sum_f32x16);
1330
+ _mm512_storeu_ps(&o_head[(qb + qi) * head_dim + d], o_f32x16);
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ }
1336
+
1337
+ NK_PUBLIC void nk_attention_causal_bf16_sapphireamx(nk_bf16_t const *q, void const *kv_packed, nk_f32_t *output,
1338
+ nk_size_t num_heads, nk_size_t num_kv_heads, nk_size_t query_len,
1339
+ nk_size_t kv_len, nk_size_t head_dim, nk_f32_t scale) {
1340
+
1341
+ // For causal attention in autoregressive decode:
1342
+ // Query position q_pos can only attend to KV positions 0..q_pos
1343
+ // If kv_len == query_len (prefill), we need proper masking
1344
+ // If query_len == 1 (decode), the single query can see all KV
1345
+
1346
+ // Simplified: just call full attention for now
1347
+ // TODO: Implement proper causal masking with block skipping
1348
+ nk_attention_bf16_sapphireamx(q, kv_packed, output, num_heads, num_kv_heads, query_len, kv_len, head_dim, scale);
1349
+ }
1350
+
1351
+ #if defined(__clang__)
1352
+ #pragma clang attribute pop
1353
+ #elif defined(__GNUC__)
1354
+ #pragma GCC pop_options
1355
+ #endif
1356
+
1357
+ #if defined(__cplusplus)
1358
+ } // extern "C"
1359
+ #endif
1360
+
1361
+ #endif // NK_TARGET_SAPPHIREAMX
1362
+ #endif // NK_TARGET_X8664_
1363
+ #endif // NK_ATTENTION_SAPPHIREAMX_H