usearch 2.21.4 → 2.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -69
- package/binding.gyp +15 -37
- package/c/CMakeLists.txt +4 -5
- package/c/README.md +8 -8
- package/c/lib.cpp +32 -14
- package/c/test.c +124 -1
- package/c/usearch.h +24 -3
- package/cpp/CMakeLists.txt +4 -0
- package/cpp/README.md +5 -5
- package/cpp/bench.cpp +97 -116
- package/cpp/test.cpp +117 -20
- package/include/usearch/index.hpp +224 -48
- package/include/usearch/index_dense.hpp +105 -40
- package/include/usearch/index_plugins.hpp +1387 -204
- package/javascript/README.md +1 -1
- package/javascript/dist/cjs/usearch.d.ts +8 -3
- package/javascript/dist/cjs/usearch.js +12 -4
- package/javascript/dist/esm/usearch.d.ts +8 -3
- package/javascript/dist/esm/usearch.js +12 -4
- package/javascript/lib.cpp +17 -0
- package/javascript/usearch.test.js +117 -94
- package/javascript/usearch.ts +14 -6
- package/numkong/.clang-tidy +30 -0
- package/{simsimd → numkong}/.cmake-format.py +18 -0
- package/numkong/.editorconfig +12 -0
- package/numkong/.git-blame-ignore-revs +123 -0
- package/numkong/.mailmap +15 -0
- package/numkong/.spi.yml +11 -0
- package/numkong/.swift-format +14 -0
- package/numkong/CITATION.cff +81 -0
- package/numkong/CONTRIBUTING.md +500 -0
- package/numkong/Cargo.toml +54 -0
- package/numkong/MANIFEST.in +8 -0
- package/numkong/Package.swift +84 -0
- package/numkong/README.md +569 -0
- package/numkong/bench/README.md +217 -0
- package/numkong/bench/bench-browser.html +323 -0
- package/numkong/bench/bench.cpp +344 -0
- package/numkong/bench/bench.hpp +816 -0
- package/numkong/bench/bench.js +148 -0
- package/numkong/bench/bench.mjs +616 -0
- package/numkong/bench/bench_attention.cpp +113 -0
- package/numkong/bench/bench_cast.cpp +264 -0
- package/numkong/bench/bench_cross_amx.cpp +62 -0
- package/numkong/bench/bench_cross_arm.cpp +245 -0
- package/numkong/bench/bench_cross_blas.cpp +309 -0
- package/numkong/bench/bench_cross_loongarch.cpp +106 -0
- package/numkong/bench/bench_cross_power.cpp +84 -0
- package/numkong/bench/bench_cross_rvv.cpp +121 -0
- package/numkong/bench/bench_cross_serial.cpp +151 -0
- package/numkong/bench/bench_cross_sme.cpp +163 -0
- package/numkong/bench/bench_cross_wasm.cpp +142 -0
- package/numkong/bench/bench_cross_x86.cpp +455 -0
- package/numkong/bench/bench_curved.cpp +210 -0
- package/numkong/bench/bench_dot.cpp +315 -0
- package/numkong/bench/bench_each.cpp +503 -0
- package/numkong/bench/bench_geospatial.cpp +112 -0
- package/numkong/bench/bench_maxsim.cpp +154 -0
- package/numkong/bench/bench_mesh.cpp +148 -0
- package/numkong/bench/bench_probability.cpp +60 -0
- package/numkong/bench/bench_reduce.cpp +371 -0
- package/numkong/bench/bench_set.cpp +73 -0
- package/numkong/bench/bench_sparse.cpp +198 -0
- package/numkong/bench/bench_spatial.cpp +347 -0
- package/numkong/bench/bench_trigonometry.cpp +137 -0
- package/numkong/bench/swift/Bench.swift +506 -0
- package/numkong/binding.gyp +169 -0
- package/numkong/build.rs +484 -0
- package/numkong/c/dispatch.h +512 -0
- package/numkong/c/dispatch_bf16.c +447 -0
- package/numkong/c/dispatch_bf16c.c +52 -0
- package/numkong/c/dispatch_e2m3.c +296 -0
- package/numkong/c/dispatch_e3m2.c +284 -0
- package/numkong/c/dispatch_e4m3.c +318 -0
- package/numkong/c/dispatch_e5m2.c +316 -0
- package/numkong/c/dispatch_f16.c +452 -0
- package/numkong/c/dispatch_f16c.c +58 -0
- package/numkong/c/dispatch_f32.c +434 -0
- package/numkong/c/dispatch_f32c.c +99 -0
- package/numkong/c/dispatch_f64.c +348 -0
- package/numkong/c/dispatch_f64c.c +98 -0
- package/numkong/c/dispatch_i16.c +96 -0
- package/numkong/c/dispatch_i32.c +89 -0
- package/numkong/c/dispatch_i4.c +153 -0
- package/numkong/c/dispatch_i64.c +86 -0
- package/numkong/c/dispatch_i8.c +348 -0
- package/numkong/c/dispatch_other.c +348 -0
- package/numkong/c/dispatch_u1.c +193 -0
- package/numkong/c/dispatch_u16.c +124 -0
- package/numkong/c/dispatch_u32.c +118 -0
- package/numkong/c/dispatch_u4.c +153 -0
- package/numkong/c/dispatch_u64.c +102 -0
- package/numkong/c/dispatch_u8.c +364 -0
- package/numkong/c/numkong.c +940 -0
- package/numkong/cmake/nk_arm_isa_probes.cmake +61 -0
- package/numkong/cmake/nk_isa_probe.cmake +90 -0
- package/numkong/cmake/nk_loongarch_isa_probes.cmake +15 -0
- package/numkong/cmake/nk_math_lib.cmake +46 -0
- package/numkong/cmake/nk_power_isa_probes.cmake +15 -0
- package/numkong/cmake/nk_riscv_isa_probes.cmake +19 -0
- package/numkong/cmake/nk_x86_isa_probes.cmake +35 -0
- package/numkong/cmake/toolchain-aarch64-gnu.cmake +86 -0
- package/numkong/cmake/toolchain-android-arm64.cmake +25 -0
- package/numkong/cmake/toolchain-android-armv7.cmake +25 -0
- package/numkong/cmake/toolchain-loongarch64-gnu.cmake +68 -0
- package/numkong/cmake/toolchain-ppc64le-gnu.cmake +74 -0
- package/numkong/cmake/toolchain-riscv64-gnu.cmake +107 -0
- package/numkong/cmake/toolchain-riscv64-llvm.cmake +114 -0
- package/numkong/cmake/toolchain-wasi.cmake +77 -0
- package/numkong/cmake/toolchain-wasm.cmake +70 -0
- package/numkong/cmake/toolchain-wasm64.cmake +62 -0
- package/numkong/cmake/toolchain-x86_64-llvm.cmake +47 -0
- package/numkong/include/README.md +643 -0
- package/numkong/include/module.modulemap +129 -0
- package/numkong/include/numkong/attention/sapphireamx.h +1363 -0
- package/numkong/include/numkong/attention/sme.h +2075 -0
- package/numkong/include/numkong/attention.h +49 -0
- package/numkong/include/numkong/capabilities.h +930 -0
- package/numkong/include/numkong/cast/README.md +262 -0
- package/numkong/include/numkong/cast/diamond.h +64 -0
- package/numkong/include/numkong/cast/haswell.h +823 -0
- package/numkong/include/numkong/cast/icelake.h +475 -0
- package/numkong/include/numkong/cast/loongsonasx.h +252 -0
- package/numkong/include/numkong/cast/neon.h +1174 -0
- package/numkong/include/numkong/cast/powervsx.h +449 -0
- package/numkong/include/numkong/cast/rvv.h +970 -0
- package/numkong/include/numkong/cast/sapphire.h +262 -0
- package/numkong/include/numkong/cast/serial.h +2336 -0
- package/numkong/include/numkong/cast/skylake.h +915 -0
- package/numkong/include/numkong/cast/v128relaxed.h +537 -0
- package/numkong/include/numkong/cast.h +254 -0
- package/numkong/include/numkong/cast.hpp +44 -0
- package/numkong/include/numkong/curved/README.md +223 -0
- package/numkong/include/numkong/curved/genoa.h +182 -0
- package/numkong/include/numkong/curved/haswell.h +276 -0
- package/numkong/include/numkong/curved/neon.h +329 -0
- package/numkong/include/numkong/curved/neonbfdot.h +211 -0
- package/numkong/include/numkong/curved/rvv.h +305 -0
- package/numkong/include/numkong/curved/serial.h +207 -0
- package/numkong/include/numkong/curved/skylake.h +457 -0
- package/numkong/include/numkong/curved/smef64.h +530 -0
- package/numkong/include/numkong/curved.h +513 -0
- package/numkong/include/numkong/curved.hpp +144 -0
- package/numkong/include/numkong/dot/README.md +442 -0
- package/numkong/include/numkong/dot/alder.h +563 -0
- package/numkong/include/numkong/dot/diamond.h +158 -0
- package/numkong/include/numkong/dot/genoa.h +289 -0
- package/numkong/include/numkong/dot/haswell.h +1723 -0
- package/numkong/include/numkong/dot/icelake.h +997 -0
- package/numkong/include/numkong/dot/loongsonasx.h +671 -0
- package/numkong/include/numkong/dot/neon.h +869 -0
- package/numkong/include/numkong/dot/neonbfdot.h +243 -0
- package/numkong/include/numkong/dot/neonfhm.h +358 -0
- package/numkong/include/numkong/dot/neonfp8.h +323 -0
- package/numkong/include/numkong/dot/neonsdot.h +622 -0
- package/numkong/include/numkong/dot/powervsx.h +752 -0
- package/numkong/include/numkong/dot/rvv.h +722 -0
- package/numkong/include/numkong/dot/rvvbb.h +72 -0
- package/numkong/include/numkong/dot/rvvbf16.h +123 -0
- package/numkong/include/numkong/dot/rvvhalf.h +129 -0
- package/numkong/include/numkong/dot/sapphire.h +141 -0
- package/numkong/include/numkong/dot/serial.h +874 -0
- package/numkong/include/numkong/dot/sierra.h +405 -0
- package/numkong/include/numkong/dot/skylake.h +1116 -0
- package/numkong/include/numkong/dot/sve.h +420 -0
- package/numkong/include/numkong/dot/svebfdot.h +75 -0
- package/numkong/include/numkong/dot/svehalf.h +168 -0
- package/numkong/include/numkong/dot/svesdot.h +90 -0
- package/numkong/include/numkong/dot/v128relaxed.h +1293 -0
- package/numkong/include/numkong/dot.h +1136 -0
- package/numkong/include/numkong/dot.hpp +94 -0
- package/numkong/include/numkong/dots/README.md +496 -0
- package/numkong/include/numkong/dots/alder.h +117 -0
- package/numkong/include/numkong/dots/diamond.h +86 -0
- package/numkong/include/numkong/dots/genoa.h +100 -0
- package/numkong/include/numkong/dots/graniteamx.h +733 -0
- package/numkong/include/numkong/dots/haswell.h +310 -0
- package/numkong/include/numkong/dots/icelake.h +180 -0
- package/numkong/include/numkong/dots/loongsonasx.h +176 -0
- package/numkong/include/numkong/dots/neon.h +123 -0
- package/numkong/include/numkong/dots/neonbfdot.h +59 -0
- package/numkong/include/numkong/dots/neonfhm.h +96 -0
- package/numkong/include/numkong/dots/neonfp8.h +99 -0
- package/numkong/include/numkong/dots/neonsdot.h +144 -0
- package/numkong/include/numkong/dots/powervsx.h +194 -0
- package/numkong/include/numkong/dots/rvv.h +2593 -0
- package/numkong/include/numkong/dots/sapphireamx.h +4017 -0
- package/numkong/include/numkong/dots/serial.h +2867 -0
- package/numkong/include/numkong/dots/sierra.h +100 -0
- package/numkong/include/numkong/dots/skylake.h +207 -0
- package/numkong/include/numkong/dots/sme.h +5041 -0
- package/numkong/include/numkong/dots/smebi32.h +477 -0
- package/numkong/include/numkong/dots/smef64.h +1328 -0
- package/numkong/include/numkong/dots/smehalf.h +47 -0
- package/numkong/include/numkong/dots/v128relaxed.h +317 -0
- package/numkong/include/numkong/dots.h +3110 -0
- package/numkong/include/numkong/dots.hpp +641 -0
- package/numkong/include/numkong/each/README.md +465 -0
- package/numkong/include/numkong/each/haswell.h +1658 -0
- package/numkong/include/numkong/each/icelake.h +272 -0
- package/numkong/include/numkong/each/neon.h +1138 -0
- package/numkong/include/numkong/each/neonbfdot.h +211 -0
- package/numkong/include/numkong/each/neonhalf.h +318 -0
- package/numkong/include/numkong/each/rvv.h +1121 -0
- package/numkong/include/numkong/each/sapphire.h +343 -0
- package/numkong/include/numkong/each/serial.h +282 -0
- package/numkong/include/numkong/each/skylake.h +1562 -0
- package/numkong/include/numkong/each/v128relaxed.h +562 -0
- package/numkong/include/numkong/each.h +2232 -0
- package/numkong/include/numkong/each.hpp +434 -0
- package/numkong/include/numkong/geospatial/README.md +147 -0
- package/numkong/include/numkong/geospatial/haswell.h +633 -0
- package/numkong/include/numkong/geospatial/neon.h +615 -0
- package/numkong/include/numkong/geospatial/rvv.h +701 -0
- package/numkong/include/numkong/geospatial/serial.h +309 -0
- package/numkong/include/numkong/geospatial/skylake.h +613 -0
- package/numkong/include/numkong/geospatial/v128relaxed.h +652 -0
- package/numkong/include/numkong/geospatial.h +453 -0
- package/numkong/include/numkong/geospatial.hpp +235 -0
- package/numkong/include/numkong/matrix.hpp +336 -0
- package/numkong/include/numkong/maxsim/README.md +187 -0
- package/numkong/include/numkong/maxsim/alder.h +511 -0
- package/numkong/include/numkong/maxsim/genoa.h +115 -0
- package/numkong/include/numkong/maxsim/haswell.h +553 -0
- package/numkong/include/numkong/maxsim/icelake.h +480 -0
- package/numkong/include/numkong/maxsim/neonsdot.h +394 -0
- package/numkong/include/numkong/maxsim/sapphireamx.h +877 -0
- package/numkong/include/numkong/maxsim/serial.h +490 -0
- package/numkong/include/numkong/maxsim/sme.h +993 -0
- package/numkong/include/numkong/maxsim/v128relaxed.h +280 -0
- package/numkong/include/numkong/maxsim.h +571 -0
- package/numkong/include/numkong/maxsim.hpp +133 -0
- package/numkong/include/numkong/mesh/README.md +219 -0
- package/numkong/include/numkong/mesh/haswell.h +2013 -0
- package/numkong/include/numkong/mesh/neon.h +1821 -0
- package/numkong/include/numkong/mesh/neonbfdot.h +771 -0
- package/numkong/include/numkong/mesh/rvv.h +936 -0
- package/numkong/include/numkong/mesh/serial.h +721 -0
- package/numkong/include/numkong/mesh/skylake.h +2344 -0
- package/numkong/include/numkong/mesh/v128relaxed.h +1081 -0
- package/numkong/include/numkong/mesh.h +666 -0
- package/numkong/include/numkong/mesh.hpp +701 -0
- package/numkong/include/numkong/numkong.h +78 -0
- package/numkong/include/numkong/numkong.hpp +58 -0
- package/numkong/include/numkong/probability/README.md +177 -0
- package/numkong/include/numkong/probability/haswell.h +267 -0
- package/numkong/include/numkong/probability/neon.h +255 -0
- package/numkong/include/numkong/probability/rvv.h +414 -0
- package/numkong/include/numkong/probability/serial.h +181 -0
- package/numkong/include/numkong/probability/skylake.h +324 -0
- package/numkong/include/numkong/probability.h +380 -0
- package/numkong/include/numkong/probability.hpp +120 -0
- package/numkong/include/numkong/random.h +50 -0
- package/numkong/include/numkong/random.hpp +285 -0
- package/numkong/include/numkong/reduce/README.md +552 -0
- package/numkong/include/numkong/reduce/alder.h +636 -0
- package/numkong/include/numkong/reduce/genoa.h +201 -0
- package/numkong/include/numkong/reduce/haswell.h +3785 -0
- package/numkong/include/numkong/reduce/icelake.h +549 -0
- package/numkong/include/numkong/reduce/neon.h +3980 -0
- package/numkong/include/numkong/reduce/neonbfdot.h +142 -0
- package/numkong/include/numkong/reduce/neonfhm.h +293 -0
- package/numkong/include/numkong/reduce/neonsdot.h +357 -0
- package/numkong/include/numkong/reduce/rvv.h +3533 -0
- package/numkong/include/numkong/reduce/serial.h +753 -0
- package/numkong/include/numkong/reduce/sierra.h +338 -0
- package/numkong/include/numkong/reduce/skylake.h +3794 -0
- package/numkong/include/numkong/reduce/sve.h +52 -0
- package/numkong/include/numkong/reduce/v128relaxed.h +2302 -0
- package/numkong/include/numkong/reduce.h +1582 -0
- package/numkong/include/numkong/reduce.hpp +778 -0
- package/numkong/include/numkong/scalar/README.md +89 -0
- package/numkong/include/numkong/scalar/haswell.h +122 -0
- package/numkong/include/numkong/scalar/loongsonasx.h +74 -0
- package/numkong/include/numkong/scalar/neon.h +122 -0
- package/numkong/include/numkong/scalar/neonhalf.h +70 -0
- package/numkong/include/numkong/scalar/powervsx.h +96 -0
- package/numkong/include/numkong/scalar/rvv.h +211 -0
- package/numkong/include/numkong/scalar/sapphire.h +74 -0
- package/numkong/include/numkong/scalar/serial.h +332 -0
- package/numkong/include/numkong/scalar/v128relaxed.h +56 -0
- package/numkong/include/numkong/scalar.h +696 -0
- package/numkong/include/numkong/set/README.md +179 -0
- package/numkong/include/numkong/set/haswell.h +334 -0
- package/numkong/include/numkong/set/icelake.h +485 -0
- package/numkong/include/numkong/set/loongsonasx.h +181 -0
- package/numkong/include/numkong/set/neon.h +363 -0
- package/numkong/include/numkong/set/powervsx.h +326 -0
- package/numkong/include/numkong/set/rvv.h +226 -0
- package/numkong/include/numkong/set/rvvbb.h +117 -0
- package/numkong/include/numkong/set/serial.h +174 -0
- package/numkong/include/numkong/set/sve.h +187 -0
- package/numkong/include/numkong/set/v128relaxed.h +240 -0
- package/numkong/include/numkong/set.h +471 -0
- package/numkong/include/numkong/set.hpp +114 -0
- package/numkong/include/numkong/sets/README.md +149 -0
- package/numkong/include/numkong/sets/haswell.h +63 -0
- package/numkong/include/numkong/sets/icelake.h +66 -0
- package/numkong/include/numkong/sets/loongsonasx.h +52 -0
- package/numkong/include/numkong/sets/neon.h +61 -0
- package/numkong/include/numkong/sets/powervsx.h +65 -0
- package/numkong/include/numkong/sets/serial.h +43 -0
- package/numkong/include/numkong/sets/smebi32.h +1135 -0
- package/numkong/include/numkong/sets/v128relaxed.h +58 -0
- package/numkong/include/numkong/sets.h +382 -0
- package/numkong/include/numkong/sparse/README.md +156 -0
- package/numkong/include/numkong/sparse/icelake.h +463 -0
- package/numkong/include/numkong/sparse/neon.h +288 -0
- package/numkong/include/numkong/sparse/serial.h +117 -0
- package/numkong/include/numkong/sparse/sve2.h +504 -0
- package/numkong/include/numkong/sparse/turin.h +322 -0
- package/numkong/include/numkong/sparse.h +363 -0
- package/numkong/include/numkong/sparse.hpp +113 -0
- package/numkong/include/numkong/spatial/README.md +439 -0
- package/numkong/include/numkong/spatial/alder.h +604 -0
- package/numkong/include/numkong/spatial/diamond.h +240 -0
- package/numkong/include/numkong/spatial/genoa.h +222 -0
- package/numkong/include/numkong/spatial/haswell.h +979 -0
- package/numkong/include/numkong/spatial/icelake.h +1067 -0
- package/numkong/include/numkong/spatial/loongsonasx.h +483 -0
- package/numkong/include/numkong/spatial/neon.h +846 -0
- package/numkong/include/numkong/spatial/neonbfdot.h +164 -0
- package/numkong/include/numkong/spatial/neonfp8.h +258 -0
- package/numkong/include/numkong/spatial/neonsdot.h +429 -0
- package/numkong/include/numkong/spatial/powervsx.h +738 -0
- package/numkong/include/numkong/spatial/rvv.h +991 -0
- package/numkong/include/numkong/spatial/rvvbf16.h +128 -0
- package/numkong/include/numkong/spatial/rvvhalf.h +120 -0
- package/numkong/include/numkong/spatial/serial.h +347 -0
- package/numkong/include/numkong/spatial/sierra.h +516 -0
- package/numkong/include/numkong/spatial/skylake.h +605 -0
- package/numkong/include/numkong/spatial/sve.h +246 -0
- package/numkong/include/numkong/spatial/svebfdot.h +125 -0
- package/numkong/include/numkong/spatial/svehalf.h +136 -0
- package/numkong/include/numkong/spatial/svesdot.h +143 -0
- package/numkong/include/numkong/spatial/v128relaxed.h +969 -0
- package/numkong/include/numkong/spatial.h +1681 -0
- package/numkong/include/numkong/spatial.hpp +183 -0
- package/numkong/include/numkong/spatials/README.md +580 -0
- package/numkong/include/numkong/spatials/alder.h +94 -0
- package/numkong/include/numkong/spatials/diamond.h +82 -0
- package/numkong/include/numkong/spatials/genoa.h +94 -0
- package/numkong/include/numkong/spatials/graniteamx.h +173 -0
- package/numkong/include/numkong/spatials/haswell.h +219 -0
- package/numkong/include/numkong/spatials/icelake.h +113 -0
- package/numkong/include/numkong/spatials/loongsonasx.h +153 -0
- package/numkong/include/numkong/spatials/neon.h +109 -0
- package/numkong/include/numkong/spatials/neonbfdot.h +60 -0
- package/numkong/include/numkong/spatials/neonfhm.h +92 -0
- package/numkong/include/numkong/spatials/neonfp8.h +111 -0
- package/numkong/include/numkong/spatials/neonsdot.h +143 -0
- package/numkong/include/numkong/spatials/powervsx.h +153 -0
- package/numkong/include/numkong/spatials/rvv.h +1976 -0
- package/numkong/include/numkong/spatials/sapphireamx.h +1149 -0
- package/numkong/include/numkong/spatials/serial.h +248 -0
- package/numkong/include/numkong/spatials/sierra.h +96 -0
- package/numkong/include/numkong/spatials/skylake.h +184 -0
- package/numkong/include/numkong/spatials/sme.h +1927 -0
- package/numkong/include/numkong/spatials/smef64.h +483 -0
- package/numkong/include/numkong/spatials/v128relaxed.h +240 -0
- package/numkong/include/numkong/spatials.h +3374 -0
- package/numkong/include/numkong/spatials.hpp +509 -0
- package/numkong/include/numkong/tensor.hpp +1981 -0
- package/numkong/include/numkong/trigonometry/README.md +184 -0
- package/numkong/include/numkong/trigonometry/haswell.h +653 -0
- package/numkong/include/numkong/trigonometry/neon.h +638 -0
- package/numkong/include/numkong/trigonometry/rvv.h +700 -0
- package/numkong/include/numkong/trigonometry/serial.h +703 -0
- package/numkong/include/numkong/trigonometry/skylake.h +725 -0
- package/numkong/include/numkong/trigonometry/v128relaxed.h +667 -0
- package/numkong/include/numkong/trigonometry.h +467 -0
- package/numkong/include/numkong/trigonometry.hpp +166 -0
- package/numkong/include/numkong/types.h +1695 -0
- package/numkong/include/numkong/types.hpp +6027 -0
- package/numkong/include/numkong/vector.hpp +766 -0
- package/numkong/javascript/@numkong-darwin-arm64/package.json +26 -0
- package/numkong/javascript/@numkong-darwin-x64/package.json +26 -0
- package/numkong/javascript/@numkong-linux-arm64/package.json +25 -0
- package/numkong/javascript/@numkong-linux-x64/package.json +25 -0
- package/numkong/javascript/@numkong-win32-arm64/package.json +25 -0
- package/numkong/javascript/@numkong-win32-x64/package.json +25 -0
- package/numkong/javascript/README.md +254 -0
- package/numkong/javascript/node-gyp-build.d.ts +4 -0
- package/numkong/javascript/numkong-browser.ts +40 -0
- package/numkong/javascript/numkong-wasm.ts +750 -0
- package/numkong/javascript/numkong.c +712 -0
- package/numkong/javascript/numkong.ts +613 -0
- package/{simsimd → numkong}/javascript/tsconfig-base.json +6 -3
- package/numkong/javascript/tsconfig-cjs.json +13 -0
- package/{simsimd → numkong}/javascript/tsconfig-esm.json +1 -1
- package/numkong/javascript/types.ts +784 -0
- package/numkong/numkong.gypi +104 -0
- package/numkong/package.json +108 -0
- package/numkong/probes/arm_neon.c +8 -0
- package/numkong/probes/arm_neon_bfdot.c +9 -0
- package/numkong/probes/arm_neon_fhm.c +9 -0
- package/numkong/probes/arm_neon_half.c +8 -0
- package/numkong/probes/arm_neon_sdot.c +9 -0
- package/numkong/probes/arm_neonfp8.c +9 -0
- package/numkong/probes/arm_sme.c +16 -0
- package/numkong/probes/arm_sme2.c +16 -0
- package/numkong/probes/arm_sme2p1.c +16 -0
- package/numkong/probes/arm_sme_bf16.c +16 -0
- package/numkong/probes/arm_sme_bi32.c +16 -0
- package/numkong/probes/arm_sme_f64.c +16 -0
- package/numkong/probes/arm_sme_fa64.c +14 -0
- package/numkong/probes/arm_sme_half.c +16 -0
- package/numkong/probes/arm_sme_lut2.c +15 -0
- package/numkong/probes/arm_sve.c +18 -0
- package/numkong/probes/arm_sve2.c +20 -0
- package/numkong/probes/arm_sve2p1.c +18 -0
- package/numkong/probes/arm_sve_bfdot.c +20 -0
- package/numkong/probes/arm_sve_half.c +18 -0
- package/numkong/probes/arm_sve_sdot.c +21 -0
- package/numkong/probes/loongarch_lasx.c +12 -0
- package/numkong/probes/power_vsx.c +12 -0
- package/numkong/probes/probe.js +127 -0
- package/numkong/probes/riscv_rvv.c +14 -0
- package/numkong/probes/riscv_rvv_bb.c +15 -0
- package/numkong/probes/riscv_rvv_bf16.c +17 -0
- package/numkong/probes/riscv_rvv_half.c +14 -0
- package/numkong/probes/wasm_v128relaxed.c +11 -0
- package/numkong/probes/x86_alder.c +17 -0
- package/numkong/probes/x86_diamond.c +17 -0
- package/numkong/probes/x86_genoa.c +17 -0
- package/numkong/probes/x86_graniteamx.c +19 -0
- package/numkong/probes/x86_haswell.c +11 -0
- package/numkong/probes/x86_icelake.c +17 -0
- package/numkong/probes/x86_sapphire.c +16 -0
- package/numkong/probes/x86_sapphireamx.c +18 -0
- package/numkong/probes/x86_sierra.c +17 -0
- package/numkong/probes/x86_skylake.c +15 -0
- package/numkong/probes/x86_turin.c +17 -0
- package/numkong/rust/README.md +698 -0
- package/numkong/rust/capabilities.rs +96 -0
- package/numkong/rust/cast.rs +314 -0
- package/numkong/rust/curved.rs +391 -0
- package/numkong/rust/each.rs +2721 -0
- package/numkong/rust/geospatial.rs +343 -0
- package/numkong/rust/matrix.rs +5424 -0
- package/numkong/rust/maxsim.rs +540 -0
- package/numkong/rust/mesh.rs +837 -0
- package/numkong/rust/numkong.rs +409 -0
- package/numkong/rust/probability.rs +272 -0
- package/numkong/rust/reduce.rs +1249 -0
- package/numkong/rust/set.rs +194 -0
- package/numkong/rust/sparse.rs +513 -0
- package/numkong/rust/spatial.rs +1815 -0
- package/numkong/rust/tensor.rs +7565 -0
- package/numkong/rust/types.rs +3666 -0
- package/numkong/rust/vector.rs +1899 -0
- package/numkong/rustfmt.toml +2 -0
- package/numkong/swift/Matrix.swift +1388 -0
- package/numkong/swift/MaxSim.swift +151 -0
- package/numkong/swift/NumKong.swift +283 -0
- package/numkong/swift/Numerics.swift +329 -0
- package/numkong/swift/README.md +433 -0
- package/numkong/swift/Scalar.swift +741 -0
- package/numkong/swift/Tensor.swift +209 -0
- package/numkong/test/README.md +315 -0
- package/numkong/test/playwright.config.ts +16 -0
- package/numkong/test/swift/Test.swift +534 -0
- package/numkong/test/test-browser-bundle-runner.mjs +36 -0
- package/numkong/test/test-browser-bundle.html +188 -0
- package/numkong/test/test-browser-runner.mjs +43 -0
- package/numkong/test/test-browser.html +305 -0
- package/numkong/test/test-dtypes.mjs +453 -0
- package/numkong/test/test-wasm.mjs +267 -0
- package/numkong/test/test.cpp +442 -0
- package/numkong/test/test.hpp +580 -0
- package/numkong/test/test.mjs +363 -0
- package/numkong/test/test_base.py +1041 -0
- package/numkong/test/test_cast.cpp +242 -0
- package/numkong/test/test_cross.hpp +551 -0
- package/numkong/test/test_cross.py +467 -0
- package/numkong/test/test_cross_amx.cpp +50 -0
- package/numkong/test/test_cross_arm.cpp +225 -0
- package/numkong/test/test_cross_blas.cpp +354 -0
- package/numkong/test/test_cross_loongarch.cpp +90 -0
- package/numkong/test/test_cross_power.cpp +73 -0
- package/numkong/test/test_cross_rvv.cpp +109 -0
- package/numkong/test/test_cross_serial.cpp +181 -0
- package/numkong/test/test_cross_sme.cpp +148 -0
- package/numkong/test/test_cross_wasm.cpp +141 -0
- package/numkong/test/test_cross_x86.cpp +442 -0
- package/numkong/test/test_curved.cpp +182 -0
- package/numkong/test/test_curved.py +207 -0
- package/numkong/test/test_dot.cpp +353 -0
- package/numkong/test/test_dot.py +296 -0
- package/numkong/test/test_each.cpp +437 -0
- package/numkong/test/test_each.py +756 -0
- package/numkong/test/test_geospatial.cpp +155 -0
- package/numkong/test/test_geospatial.py +241 -0
- package/numkong/test/test_maxsim.cpp +138 -0
- package/numkong/test/test_maxsim.py +209 -0
- package/numkong/test/test_mesh.cpp +203 -0
- package/numkong/test/test_mesh.py +394 -0
- package/numkong/test/test_probability.cpp +136 -0
- package/numkong/test/test_probability.py +166 -0
- package/numkong/test/test_reduce.cpp +563 -0
- package/numkong/test/test_reduce.py +781 -0
- package/numkong/test/test_set.cpp +151 -0
- package/numkong/test/test_set.py +120 -0
- package/numkong/test/test_similarities.py +579 -0
- package/numkong/test/test_sparse.cpp +121 -0
- package/numkong/test/test_sparse.py +118 -0
- package/numkong/test/test_spatial.cpp +518 -0
- package/numkong/test/test_spatial.py +269 -0
- package/numkong/test/test_tensor.cpp +884 -0
- package/numkong/test/test_tensor.py +1417 -0
- package/numkong/test/test_trigonometry.cpp +156 -0
- package/numkong/test/test_trigonometry.py +180 -0
- package/package.json +7 -8
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/fp16/CMakeLists.txt +0 -176
- package/fp16/LICENSE +0 -11
- package/fp16/README.md +0 -20
- package/fp16/bench/alt-element.cc +0 -89
- package/fp16/bench/from-alt-array.cc +0 -175
- package/fp16/bench/from-ieee-array.cc +0 -399
- package/fp16/bench/ieee-element.cc +0 -272
- package/fp16/bench/to-alt-array.cc +0 -91
- package/fp16/bench/to-ieee-array.cc +0 -303
- package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
- package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
- package/fp16/cmake/DownloadPSimd.cmake +0 -15
- package/fp16/configure.py +0 -65
- package/fp16/confu.yaml +0 -8
- package/fp16/include/fp16/__init__.py +0 -0
- package/fp16/include/fp16/avx.py +0 -50
- package/fp16/include/fp16/avx2.py +0 -53
- package/fp16/include/fp16/bitcasts.h +0 -92
- package/fp16/include/fp16/fp16.h +0 -451
- package/fp16/include/fp16/psimd.h +0 -131
- package/fp16/include/fp16.h +0 -11
- package/fp16/jni/Android.mk +0 -6
- package/fp16/jni/Application.mk +0 -4
- package/fp16/test/alt-from-fp32-value.cc +0 -498
- package/fp16/test/alt-to-fp32-bits.cc +0 -263
- package/fp16/test/alt-to-fp32-psimd.cc +0 -145
- package/fp16/test/alt-to-fp32-value.cc +0 -348
- package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
- package/fp16/test/bitcasts.cc +0 -56
- package/fp16/test/ieee-from-fp32-value.cc +0 -523
- package/fp16/test/ieee-to-fp32-bits.cc +0 -333
- package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
- package/fp16/test/ieee-to-fp32-value.cc +0 -420
- package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
- package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
- package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
- package/fp16/test/peachpy/stubs.py +0 -38
- package/fp16/test/tables.cc +0 -530
- package/fp16/test/tables.h +0 -13
- package/fp16/third-party/THHalf.h +0 -92
- package/fp16/third-party/eigen-half.h +0 -170
- package/fp16/third-party/float16-compressor.h +0 -80
- package/fp16/third-party/half.hpp +0 -2916
- package/fp16/third-party/npy-halffloat.h +0 -328
- package/simsimd/.git-blame-ignore-revs +0 -2
- package/simsimd/CONTRIBUTING.md +0 -321
- package/simsimd/Cargo.toml +0 -49
- package/simsimd/MANIFEST.in +0 -4
- package/simsimd/Package.swift +0 -28
- package/simsimd/README.md +0 -1493
- package/simsimd/binding.gyp +0 -35
- package/simsimd/build.rs +0 -70
- package/simsimd/c/lib.c +0 -374
- package/simsimd/include/module.modulemap +0 -4
- package/simsimd/include/simsimd/binary.h +0 -486
- package/simsimd/include/simsimd/curved.h +0 -1541
- package/simsimd/include/simsimd/dot.h +0 -1853
- package/simsimd/include/simsimd/elementwise.h +0 -2465
- package/simsimd/include/simsimd/geospatial.h +0 -43
- package/simsimd/include/simsimd/mesh.h +0 -69
- package/simsimd/include/simsimd/probability.h +0 -606
- package/simsimd/include/simsimd/simsimd.h +0 -2567
- package/simsimd/include/simsimd/sparse.h +0 -1384
- package/simsimd/include/simsimd/spatial.h +0 -2335
- package/simsimd/include/simsimd/types.h +0 -666
- package/simsimd/javascript/fallback.ts +0 -233
- package/simsimd/javascript/lib.c +0 -125
- package/simsimd/javascript/node-gyp-build.d.ts +0 -1
- package/simsimd/javascript/simsimd.ts +0 -179
- package/simsimd/javascript/tsconfig-cjs.json +0 -8
- package/simsimd/package-lock.json +0 -1761
- package/simsimd/package.json +0 -61
- package/simsimd/rust/lib.rs +0 -1821
- package/simsimd/scripts/bench.cxx +0 -1170
- package/simsimd/scripts/bench.ipynb +0 -187
- package/simsimd/scripts/bench.js +0 -127
- package/simsimd/scripts/bench.rs +0 -876
- package/simsimd/scripts/bench_vectors.py +0 -734
- package/simsimd/scripts/bench_vectors_live.py +0 -213
- package/simsimd/scripts/test.c +0 -187
- package/simsimd/scripts/test.mjs +0 -220
- package/simsimd/scripts/test.py +0 -1505
- package/simsimd/swift/SimSIMD.swift +0 -149
- package/simsimd/swift/Test.swift +0 -128
- /package/{simsimd → numkong}/CODE_OF_CONDUCT.md +0 -0
- /package/{simsimd → numkong}/LICENSE +0 -0
- /package/{simsimd → numkong}/javascript/dist-package-cjs.json +0 -0
- /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
|