usearch 2.21.3 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/binding.gyp +15 -37
- package/c/CMakeLists.txt +4 -5
- package/c/README.md +7 -7
- package/c/lib.cpp +32 -14
- package/c/test.c +83 -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 +235 -52
- package/include/usearch/index_dense.hpp +105 -40
- package/include/usearch/index_plugins.hpp +1387 -204
- 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.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,1417 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test tensor API: constructors, slicing, reductions, scalar types, NumPy interop.
|
|
3
|
+
|
|
4
|
+
Dtypes: float64, float32, int8, int32, bfloat16, float8_e4m3, float8_e5m2, float6_e2m3, float6_e3m2.
|
|
5
|
+
Matches C++ suite: test_tensor.cpp.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import concurrent.futures
|
|
9
|
+
import multiprocessing
|
|
10
|
+
import sys
|
|
11
|
+
import sysconfig
|
|
12
|
+
import time
|
|
13
|
+
import warnings
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
import numpy as np # static-analysis-only; the runtime try/except below is authoritative
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
numpy_available = True
|
|
26
|
+
except Exception:
|
|
27
|
+
numpy_available = False
|
|
28
|
+
|
|
29
|
+
import numkong as nk
|
|
30
|
+
from test_base import (
|
|
31
|
+
NK_ATOL,
|
|
32
|
+
NK_RTOL,
|
|
33
|
+
assert_allclose,
|
|
34
|
+
dense_dimensions,
|
|
35
|
+
f32_downcast_to_bf16,
|
|
36
|
+
make_nk,
|
|
37
|
+
ml_dtypes_available,
|
|
38
|
+
nk_seed, # noqa: F401 — pytest fixture
|
|
39
|
+
numpy_available,
|
|
40
|
+
precise_decimal,
|
|
41
|
+
randomized_repetitions_count,
|
|
42
|
+
seed_rng, # noqa: F401 — pytest fixture (autouse)
|
|
43
|
+
to_array,
|
|
44
|
+
tolerances_for_dtype,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
import ml_dtypes
|
|
49
|
+
except ImportError:
|
|
50
|
+
ml_dtypes = None # type: ignore[assignment]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def precise_sum(a, dtype=None):
|
|
54
|
+
"""High-precision sum via Python Decimal."""
|
|
55
|
+
with precise_decimal(dtype) as (upcast, _sqrt, _ln):
|
|
56
|
+
return float(sum(upcast(x) for x in np.asarray(a).flat))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def precise_min(a, dtype=None):
|
|
60
|
+
"""Exact min — comparison is exact on IEEE floats."""
|
|
61
|
+
return float(min(float(x) for x in np.asarray(a).flat))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def precise_max(a, dtype=None):
|
|
65
|
+
"""Exact max — comparison is exact on IEEE floats."""
|
|
66
|
+
return float(max(float(x) for x in np.asarray(a).flat))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def precise_add(a, b, dtype=None):
|
|
70
|
+
"""High-precision element-wise add via Python Decimal."""
|
|
71
|
+
with precise_decimal(dtype) as (upcast, _sqrt, _ln):
|
|
72
|
+
return [float(upcast(x) + upcast(y)) for x, y in zip(np.asarray(a).flat, np.asarray(b).flat)]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def precise_subtract(a, b, dtype=None):
|
|
76
|
+
"""High-precision element-wise subtract via Python Decimal."""
|
|
77
|
+
with precise_decimal(dtype) as (upcast, _sqrt, _ln):
|
|
78
|
+
return [float(upcast(x) - upcast(y)) for x, y in zip(np.asarray(a).flat, np.asarray(b).flat)]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def precise_multiply(a, b, dtype=None):
|
|
82
|
+
"""High-precision element-wise multiply via Python Decimal."""
|
|
83
|
+
with precise_decimal(dtype) as (upcast, _sqrt, _ln):
|
|
84
|
+
return [float(upcast(x) * upcast(y)) for x, y in zip(np.asarray(a).flat, np.asarray(b).flat)]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def precise_negate(a, dtype=None):
|
|
88
|
+
"""Exact negate — sign flip has no precision loss."""
|
|
89
|
+
return [-float(x) for x in np.asarray(a).flat]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
KERNELS_TENSOR: dict[str, tuple[Callable, Callable, Callable | None]] = {
|
|
93
|
+
"sum": (lambda a: np.sum(np.asarray(a)), lambda a: a.sum(), precise_sum),
|
|
94
|
+
"min": (lambda a: np.min(np.asarray(a)), lambda a: a.min(), precise_min),
|
|
95
|
+
"max": (lambda a: np.max(np.asarray(a)), lambda a: a.max(), precise_max),
|
|
96
|
+
"argmin": (lambda a: np.argmin(np.asarray(a)), lambda a: a.argmin(), None),
|
|
97
|
+
"argmax": (lambda a: np.argmax(np.asarray(a)), lambda a: a.argmax(), None),
|
|
98
|
+
"add": (lambda a, b: np.add(np.asarray(a), np.asarray(b)), lambda a, b: a + b, precise_add),
|
|
99
|
+
"subtract": (lambda a, b: np.subtract(np.asarray(a), np.asarray(b)), lambda a, b: a - b, precise_subtract),
|
|
100
|
+
"multiply": (lambda a, b: np.multiply(np.asarray(a), np.asarray(b)), lambda a, b: a * b, precise_multiply),
|
|
101
|
+
"negate": (lambda a: -np.asarray(a), lambda a: -a, precise_negate),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_pointers_availability():
|
|
106
|
+
"""Tests the availability of pre-compiled functions for compatibility with USearch."""
|
|
107
|
+
assert nk.pointer_to_sqeuclidean("float64") != 0
|
|
108
|
+
assert nk.pointer_to_angular("float64") != 0
|
|
109
|
+
assert nk.pointer_to_inner("float64") != 0
|
|
110
|
+
|
|
111
|
+
assert nk.pointer_to_sqeuclidean("float32") != 0
|
|
112
|
+
assert nk.pointer_to_angular("float32") != 0
|
|
113
|
+
assert nk.pointer_to_inner("float32") != 0
|
|
114
|
+
|
|
115
|
+
assert nk.pointer_to_sqeuclidean("float16") != 0
|
|
116
|
+
assert nk.pointer_to_angular("float16") != 0
|
|
117
|
+
assert nk.pointer_to_inner("float16") != 0
|
|
118
|
+
|
|
119
|
+
assert nk.pointer_to_sqeuclidean("int8") != 0
|
|
120
|
+
assert nk.pointer_to_angular("int8") != 0
|
|
121
|
+
assert nk.pointer_to_inner("int8") != 0
|
|
122
|
+
|
|
123
|
+
assert nk.pointer_to_sqeuclidean("uint8") != 0
|
|
124
|
+
assert nk.pointer_to_angular("uint8") != 0
|
|
125
|
+
assert nk.pointer_to_inner("uint8") != 0
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_capabilities_list():
|
|
129
|
+
"""Tests the visibility of hardware capabilities."""
|
|
130
|
+
caps = nk.get_capabilities()
|
|
131
|
+
avx2_caps = ["haswell", "alder", "sierra"]
|
|
132
|
+
avx512_caps = ["skylake", "icelake", "genoa", "sapphire", "turin", "diamond"]
|
|
133
|
+
amx_caps = ["sapphireamx", "graniteamx"]
|
|
134
|
+
neon_caps = ["neon", "neonhalf", "neonfhm", "neonbfdot", "neonsdot", "neonfp8"]
|
|
135
|
+
sve_caps = ["sve", "svehalf", "svebfdot", "svesdot", "sve2", "sve2p1"]
|
|
136
|
+
sme_caps = ["sme", "sme2", "sme2p1", "smef64", "smehalf", "smebf16", "smebi32", "smelut2", "smefa64"]
|
|
137
|
+
rvv_caps = ["rvv", "rvvhalf", "rvvbf16", "rvvbb"]
|
|
138
|
+
power_caps = ["powervsx"]
|
|
139
|
+
loongarch_caps = ["loongsonasx"]
|
|
140
|
+
wasm_caps = ["v128relaxed"]
|
|
141
|
+
|
|
142
|
+
expected_capabilities = [
|
|
143
|
+
"serial",
|
|
144
|
+
*avx2_caps,
|
|
145
|
+
*avx512_caps,
|
|
146
|
+
*amx_caps,
|
|
147
|
+
*neon_caps,
|
|
148
|
+
*sve_caps,
|
|
149
|
+
*sme_caps,
|
|
150
|
+
*rvv_caps,
|
|
151
|
+
*power_caps,
|
|
152
|
+
*loongarch_caps,
|
|
153
|
+
*wasm_caps,
|
|
154
|
+
]
|
|
155
|
+
for cap in expected_capabilities:
|
|
156
|
+
assert cap in caps, f"Capability '{cap}' missing from get_capabilities()"
|
|
157
|
+
assert caps.get("serial") == 1
|
|
158
|
+
|
|
159
|
+
previous_value = nk.get_capabilities().get("neon")
|
|
160
|
+
nk.enable_capability("neon")
|
|
161
|
+
assert nk.get_capabilities().get("neon") == 1
|
|
162
|
+
if not previous_value:
|
|
163
|
+
nk.disable_capability("neon")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
167
|
+
@pytest.mark.parametrize(
|
|
168
|
+
"function, expected_error, args, kwargs",
|
|
169
|
+
[
|
|
170
|
+
(nk.sqeuclidean, TypeError, (), {}),
|
|
171
|
+
(nk.sqeuclidean, TypeError, (to_array([1.0]),), {}),
|
|
172
|
+
(nk.sqeuclidean, ValueError, (to_array([1.0]), to_array([1.0]), "missing_dtype"), {}),
|
|
173
|
+
(nk.sqeuclidean, TypeError, (to_array([1.0]), "invalid"), {}),
|
|
174
|
+
(nk.sqeuclidean, TypeError, (to_array([1.0]), to_array([1.0])), {"invalid_kwarg": "value"}),
|
|
175
|
+
(nk.enable_capability, TypeError, (123,), {}),
|
|
176
|
+
(nk.disable_capability, TypeError, ([],), {}),
|
|
177
|
+
(nk.mahalanobis, TypeError, (to_array([1.0]), to_array([1.0])), {}),
|
|
178
|
+
(nk.bilinear, TypeError, (to_array([1.0]),), {}),
|
|
179
|
+
(nk.angular, TypeError, (to_array([1.0]), to_array([1.0]), to_array([1.0])), {}),
|
|
180
|
+
(nk.cdist, TypeError, (to_array([[1.0]]), to_array([[1.0]]), "l2", "dos"), {}),
|
|
181
|
+
(nk.cdist, TypeError, (to_array([[1.0]]), to_array([[1.0]]), "l2"), {"metric": "l2"}),
|
|
182
|
+
(nk.angular, LookupError, (to_array([1 + 2j]), to_array([1 + 2j])), {}),
|
|
183
|
+
(nk.angular, ValueError, (to_array([1.0]), to_array([1.0, 2.0])), {}),
|
|
184
|
+
(nk.angular, TypeError, (to_array([1.0]), to_array([1], "int8")), {}),
|
|
185
|
+
(nk.angular, TypeError, (to_array([1], "float32"), to_array([1], "float16")), {}),
|
|
186
|
+
],
|
|
187
|
+
)
|
|
188
|
+
def test_invalid_argument_handling(function, expected_error, args, kwargs):
|
|
189
|
+
"""Test that functions raise appropriate errors with invalid arguments."""
|
|
190
|
+
with pytest.raises(expected_error):
|
|
191
|
+
function(*args, **kwargs)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
195
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes is not installed")
|
|
196
|
+
@pytest.mark.repeat(randomized_repetitions_count)
|
|
197
|
+
@pytest.mark.parametrize("ndim", dense_dimensions)
|
|
198
|
+
def test_bf16_conversion_vs_ml_dtypes(ndim: int):
|
|
199
|
+
"""Compare NumKong bfloat16 conversion with ml_dtypes reference implementation."""
|
|
200
|
+
a_f32 = np.random.randn(ndim).astype(np.float32)
|
|
201
|
+
_, a_nk_bf16 = f32_downcast_to_bf16(a_f32)
|
|
202
|
+
a_ml_bf16 = a_f32.astype(ml_dtypes.bfloat16)
|
|
203
|
+
ml_bits = np.asarray(a_ml_bf16.view(np.uint16), dtype=np.uint16)
|
|
204
|
+
assert np.array_equal(a_nk_bf16, ml_bits), (
|
|
205
|
+
f"BFloat16 conversion mismatch with ml_dtypes:\n"
|
|
206
|
+
f" NumKong bits: {a_nk_bf16[:5]}...\n"
|
|
207
|
+
f" ml_dtypes bits: {ml_bits[:5]}..."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
212
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes is not installed")
|
|
213
|
+
@pytest.mark.repeat(randomized_repetitions_count)
|
|
214
|
+
@pytest.mark.parametrize("ndim", dense_dimensions)
|
|
215
|
+
def test_float8_e4m3_conversion_vs_ml_dtypes(ndim: int):
|
|
216
|
+
"""Compare NumKong float8_e4m3 conversion with ml_dtypes reference implementation."""
|
|
217
|
+
a_f32 = (np.random.randn(ndim) * 10).astype(np.float32)
|
|
218
|
+
a_f32 = np.clip(a_f32, -448, 448)
|
|
219
|
+
a_ml_e4m3 = a_f32.astype(ml_dtypes.float8_e4m3fn)
|
|
220
|
+
a_nk = nk.zeros((ndim,), dtype="e4m3")
|
|
221
|
+
assert a_ml_e4m3.dtype == ml_dtypes.float8_e4m3fn
|
|
222
|
+
assert a_nk.dtype == "e4m3"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
226
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes is not installed")
|
|
227
|
+
@pytest.mark.repeat(randomized_repetitions_count)
|
|
228
|
+
@pytest.mark.parametrize("ndim", dense_dimensions)
|
|
229
|
+
def test_float8_e5m2_conversion_vs_ml_dtypes(ndim: int):
|
|
230
|
+
"""Compare NumKong float8_e5m2 conversion with ml_dtypes reference implementation."""
|
|
231
|
+
a_f32 = (np.random.randn(ndim) * 100).astype(np.float32)
|
|
232
|
+
a_f32 = np.clip(a_f32, -57344, 57344)
|
|
233
|
+
a_ml_e5m2 = a_f32.astype(ml_dtypes.float8_e5m2)
|
|
234
|
+
a_nk = nk.zeros((ndim,), dtype="e5m2")
|
|
235
|
+
assert a_ml_e5m2.dtype == ml_dtypes.float8_e5m2
|
|
236
|
+
assert a_nk.dtype == "e5m2"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@pytest.mark.parametrize("ndim", dense_dimensions)
|
|
240
|
+
def test_float6_e2m3_construction(ndim: int):
|
|
241
|
+
"""Verify that e2m3 tensors can be constructed and have the correct dtype."""
|
|
242
|
+
a_nk = nk.zeros((ndim,), dtype="e2m3")
|
|
243
|
+
assert a_nk.dtype == "e2m3"
|
|
244
|
+
assert a_nk.shape == (ndim,)
|
|
245
|
+
assert a_nk.ndim == 1
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@pytest.mark.parametrize("ndim", dense_dimensions)
|
|
249
|
+
def test_float6_e3m2_construction(ndim: int):
|
|
250
|
+
"""Verify that e3m2 tensors can be constructed and have the correct dtype."""
|
|
251
|
+
a_nk = nk.zeros((ndim,), dtype="e3m2")
|
|
252
|
+
assert a_nk.dtype == "e3m2"
|
|
253
|
+
assert a_nk.shape == (ndim,)
|
|
254
|
+
assert a_nk.ndim == 1
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_distances_tensor_properties(nk_seed: int):
|
|
258
|
+
a = nk.iota((5, 128), nk_seed, dtype="float64")
|
|
259
|
+
b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
|
|
260
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
261
|
+
|
|
262
|
+
assert hasattr(result, "shape")
|
|
263
|
+
assert hasattr(result, "dtype")
|
|
264
|
+
assert hasattr(result, "ndim")
|
|
265
|
+
assert hasattr(result, "size")
|
|
266
|
+
assert hasattr(result, "nbytes")
|
|
267
|
+
assert hasattr(result, "strides")
|
|
268
|
+
assert hasattr(result, "itemsize")
|
|
269
|
+
|
|
270
|
+
assert result.shape == (5, 7)
|
|
271
|
+
assert result.dtype == "float64"
|
|
272
|
+
assert result.ndim == 2
|
|
273
|
+
assert result.size == 35
|
|
274
|
+
assert result.nbytes == 35 * 8
|
|
275
|
+
assert result.itemsize == 8
|
|
276
|
+
assert isinstance(result.strides, tuple)
|
|
277
|
+
assert len(result.strides) == 2
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_distances_tensor_properties_1d(nk_seed: int):
|
|
281
|
+
a = nk.iota((10, 128), nk_seed, dtype="float64")
|
|
282
|
+
b = nk.iota((10, 128), nk_seed + 1, dtype="float64")
|
|
283
|
+
result = nk.sqeuclidean(a, b)
|
|
284
|
+
|
|
285
|
+
assert result.shape == (10,)
|
|
286
|
+
assert result.ndim == 1
|
|
287
|
+
assert result.size == 10
|
|
288
|
+
assert len(result.strides) == 1
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def test_distances_tensor_len(nk_seed: int):
|
|
292
|
+
a = nk.iota((5, 128), nk_seed, dtype="float64")
|
|
293
|
+
b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
|
|
294
|
+
result_2d = nk.cdist(a, b, metric="sqeuclidean")
|
|
295
|
+
assert len(result_2d) == 5
|
|
296
|
+
|
|
297
|
+
a = nk.iota((10, 128), nk_seed, dtype="float64")
|
|
298
|
+
b = nk.iota((10, 128), nk_seed + 1, dtype="float64")
|
|
299
|
+
result_1d = nk.sqeuclidean(a, b)
|
|
300
|
+
assert len(result_1d) == 10
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
304
|
+
def test_distances_tensor_indexing():
|
|
305
|
+
a = np.random.rand(5, 128).astype(np.float64)
|
|
306
|
+
b = np.random.rand(7, 128).astype(np.float64)
|
|
307
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
308
|
+
assert repr(result) # smoke-test repr doesn't crash
|
|
309
|
+
expected = np.asarray(result)
|
|
310
|
+
|
|
311
|
+
row0 = result[0]
|
|
312
|
+
assert hasattr(row0, "shape")
|
|
313
|
+
assert row0.shape == (7,)
|
|
314
|
+
|
|
315
|
+
row_last = result[-1]
|
|
316
|
+
assert row_last.shape == (7,)
|
|
317
|
+
assert_allclose(np.asarray(row_last), expected[-1])
|
|
318
|
+
|
|
319
|
+
val = result[2, 3]
|
|
320
|
+
assert isinstance(val, float)
|
|
321
|
+
assert_allclose(val, expected[2, 3])
|
|
322
|
+
|
|
323
|
+
val_neg = result[-1, -1]
|
|
324
|
+
assert isinstance(val_neg, float)
|
|
325
|
+
assert_allclose(val_neg, expected[-1, -1])
|
|
326
|
+
|
|
327
|
+
with pytest.raises(IndexError):
|
|
328
|
+
_ = result[10]
|
|
329
|
+
with pytest.raises(IndexError):
|
|
330
|
+
_ = result[0, 100]
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
334
|
+
def test_distances_tensor_iteration():
|
|
335
|
+
a = np.random.rand(5, 128).astype(np.float64)
|
|
336
|
+
b = np.random.rand(7, 128).astype(np.float64)
|
|
337
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
338
|
+
expected = np.asarray(result)
|
|
339
|
+
|
|
340
|
+
count = 0
|
|
341
|
+
for i, row in enumerate(result):
|
|
342
|
+
count += 1
|
|
343
|
+
assert row.shape == (7,)
|
|
344
|
+
assert_allclose(np.asarray(row), expected[i])
|
|
345
|
+
assert count == 5
|
|
346
|
+
|
|
347
|
+
a = np.random.rand(3, 128).astype(np.float64)
|
|
348
|
+
b = np.random.rand(3, 128).astype(np.float64)
|
|
349
|
+
result_1d = nk.sqeuclidean(a, b)
|
|
350
|
+
expected_1d = np.asarray(result_1d)
|
|
351
|
+
|
|
352
|
+
items = list(result_1d)
|
|
353
|
+
assert len(items) == 3
|
|
354
|
+
for i, item in enumerate(items):
|
|
355
|
+
assert isinstance(item, float)
|
|
356
|
+
assert_allclose(item, expected_1d[i])
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
360
|
+
def test_distances_tensor_numpy_interop():
|
|
361
|
+
a = np.random.rand(5, 128).astype(np.float64)
|
|
362
|
+
b = np.random.rand(7, 128).astype(np.float64)
|
|
363
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
364
|
+
|
|
365
|
+
arr = np.asarray(result)
|
|
366
|
+
assert isinstance(arr, np.ndarray)
|
|
367
|
+
assert arr.shape == (5, 7)
|
|
368
|
+
assert arr.dtype == np.float64
|
|
369
|
+
assert np.all(arr >= 0)
|
|
370
|
+
|
|
371
|
+
mv = memoryview(result)
|
|
372
|
+
assert mv.ndim == 2
|
|
373
|
+
assert mv.shape == (5, 7)
|
|
374
|
+
|
|
375
|
+
arr_from_mv = np.asarray(mv)
|
|
376
|
+
np.testing.assert_array_equal(arr, arr_from_mv)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def test_distances_tensor_scalar_conversion(nk_seed: int):
|
|
380
|
+
a = nk.iota((1, 128), nk_seed, dtype="float64").flatten()
|
|
381
|
+
b = nk.iota((1, 128), nk_seed + 1, dtype="float64").flatten()
|
|
382
|
+
result = nk.sqeuclidean(a, b)
|
|
383
|
+
assert isinstance(result, float)
|
|
384
|
+
assert result >= 0
|
|
385
|
+
|
|
386
|
+
a2 = nk.iota((3, 128), nk_seed, dtype="float64")
|
|
387
|
+
b2 = nk.iota((5, 128), nk_seed + 1, dtype="float64")
|
|
388
|
+
result2 = nk.cdist(a2, b2, metric="sqeuclidean")
|
|
389
|
+
with pytest.raises(TypeError):
|
|
390
|
+
float(result2)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@pytest.mark.parametrize("input_dtype", ["float32", "float64"])
|
|
394
|
+
def test_distances_tensor_dtype_consistency(input_dtype, nk_seed: int):
|
|
395
|
+
a = nk.iota((5, 128), nk_seed, dtype=input_dtype)
|
|
396
|
+
b = nk.iota((7, 128), nk_seed + 1, dtype=input_dtype)
|
|
397
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
398
|
+
assert result.dtype == "float64"
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def test_distances_tensor_strides(nk_seed: int):
|
|
402
|
+
a = nk.iota((5, 128), nk_seed, dtype="float64")
|
|
403
|
+
b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
|
|
404
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
405
|
+
|
|
406
|
+
strides = result.strides
|
|
407
|
+
assert isinstance(strides, tuple)
|
|
408
|
+
assert len(strides) == 2
|
|
409
|
+
assert strides == (7 * 8, 8)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def test_distances_tensor_array_interface(nk_seed: int):
|
|
413
|
+
a = nk.iota((5, 128), nk_seed, dtype="float64")
|
|
414
|
+
b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
|
|
415
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
416
|
+
|
|
417
|
+
ai = result.__array_interface__
|
|
418
|
+
assert isinstance(ai, dict)
|
|
419
|
+
assert "shape" in ai
|
|
420
|
+
assert "typestr" in ai
|
|
421
|
+
assert "data" in ai
|
|
422
|
+
assert "strides" in ai
|
|
423
|
+
assert "version" in ai
|
|
424
|
+
assert ai["shape"] == (5, 7)
|
|
425
|
+
assert ai["typestr"] == "<f8"
|
|
426
|
+
assert ai["version"] == 3
|
|
427
|
+
assert isinstance(ai["data"], tuple)
|
|
428
|
+
assert len(ai["data"]) == 2
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
432
|
+
def test_distances_tensor_transpose():
|
|
433
|
+
a = np.random.rand(5, 128).astype(np.float64)
|
|
434
|
+
b = np.random.rand(7, 128).astype(np.float64)
|
|
435
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
436
|
+
|
|
437
|
+
t = result.T
|
|
438
|
+
assert t.shape == (7, 5)
|
|
439
|
+
result_arr = np.asarray(result)
|
|
440
|
+
t_arr = np.asarray(t)
|
|
441
|
+
assert_allclose(result_arr.T, t_arr)
|
|
442
|
+
|
|
443
|
+
a1d = np.random.rand(10, 128).astype(np.float64)
|
|
444
|
+
b1d = np.random.rand(10, 128).astype(np.float64)
|
|
445
|
+
result1d = nk.sqeuclidean(a1d, b1d)
|
|
446
|
+
t1d = result1d.T
|
|
447
|
+
assert t1d.shape == result1d.shape
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def test_distances_tensor_str(nk_seed: int):
|
|
451
|
+
a = nk.iota((3, 128), nk_seed, dtype="float64")
|
|
452
|
+
b = nk.iota((4, 128), nk_seed + 1, dtype="float64")
|
|
453
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
454
|
+
|
|
455
|
+
s = str(result)
|
|
456
|
+
assert len(s) > 0
|
|
457
|
+
assert any(c.isdigit() for c in s)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def test_distances_tensor_equality(nk_seed: int):
|
|
461
|
+
a = nk.iota((5, 4), nk_seed, dtype="float32")
|
|
462
|
+
b = nk.iota((7, 4), nk_seed + 20, dtype="float32")
|
|
463
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
464
|
+
|
|
465
|
+
copy = result.copy()
|
|
466
|
+
assert result == copy
|
|
467
|
+
assert not (result != copy) # noqa: SIM202 — intentionally tests __ne__
|
|
468
|
+
|
|
469
|
+
a2 = nk.iota((5, 4), nk_seed + 100, dtype="float32")
|
|
470
|
+
b2 = nk.iota((7, 4), nk_seed + 200, dtype="float32")
|
|
471
|
+
result2 = nk.cdist(a2, b2, metric="sqeuclidean")
|
|
472
|
+
assert result != result2
|
|
473
|
+
|
|
474
|
+
a3 = nk.iota((3, 4), nk_seed, dtype="float32")
|
|
475
|
+
b3 = nk.iota((4, 4), nk_seed + 20, dtype="float32")
|
|
476
|
+
result3 = nk.cdist(a3, b3, metric="sqeuclidean")
|
|
477
|
+
assert result != result3
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def test_distances_tensor_inf_nan_propagation(nk_seed: int):
|
|
481
|
+
"""Verify that inf/nan inputs propagate through cdist without crashing."""
|
|
482
|
+
normal = nk.iota((3, 4), nk_seed, dtype="float64")
|
|
483
|
+
|
|
484
|
+
inf_tensor = nk.full((2, 4), float("inf"), dtype="float64")
|
|
485
|
+
result_inf = nk.cdist(inf_tensor, normal, metric="sqeuclidean")
|
|
486
|
+
assert result_inf.shape == (2, 3)
|
|
487
|
+
assert result_inf.dtype == "float64"
|
|
488
|
+
flat = result_inf.flatten()
|
|
489
|
+
for i in range(result_inf.size):
|
|
490
|
+
v = float(flat[i])
|
|
491
|
+
assert v == float("inf") or v != v # inf or nan
|
|
492
|
+
|
|
493
|
+
nan_tensor = nk.full((2, 4), float("nan"), dtype="float64")
|
|
494
|
+
result_nan = nk.cdist(nan_tensor, normal, metric="sqeuclidean")
|
|
495
|
+
assert result_nan.shape == (2, 3)
|
|
496
|
+
assert result_nan.dtype == "float64"
|
|
497
|
+
flat_nan = result_nan.flatten()
|
|
498
|
+
for i in range(result_nan.size):
|
|
499
|
+
assert float(flat_nan[i]) != float(flat_nan[i]) # all nan
|
|
500
|
+
|
|
501
|
+
s = str(result_inf)
|
|
502
|
+
assert len(s) > 0
|
|
503
|
+
assert any(c.isdigit() or c in "inaINAf" for c in s)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
507
|
+
def test_distances_tensor_copy():
|
|
508
|
+
a = np.random.rand(5, 128).astype(np.float64)
|
|
509
|
+
b = np.random.rand(7, 128).astype(np.float64)
|
|
510
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
511
|
+
|
|
512
|
+
copy = result.copy()
|
|
513
|
+
assert copy.shape == result.shape
|
|
514
|
+
assert copy.dtype == result.dtype
|
|
515
|
+
assert result == copy
|
|
516
|
+
|
|
517
|
+
result_arr = np.asarray(result)
|
|
518
|
+
copy_arr = np.asarray(copy)
|
|
519
|
+
np.testing.assert_array_equal(result_arr, copy_arr)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def test_distances_tensor_reshape(nk_seed: int):
|
|
523
|
+
a = nk.iota((5, 128), nk_seed, dtype="float64")
|
|
524
|
+
b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
|
|
525
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
526
|
+
|
|
527
|
+
flat = result.reshape(35)
|
|
528
|
+
assert flat.shape == (35,)
|
|
529
|
+
assert flat.size == 35
|
|
530
|
+
|
|
531
|
+
back = flat.reshape(5, 7)
|
|
532
|
+
assert back.shape == (5, 7)
|
|
533
|
+
|
|
534
|
+
reshaped = result.reshape((7, 5))
|
|
535
|
+
assert reshaped.shape == (7, 5)
|
|
536
|
+
|
|
537
|
+
with pytest.raises(ValueError):
|
|
538
|
+
result.reshape(10, 10)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
542
|
+
def test_distances_tensor_slicing():
|
|
543
|
+
a = np.random.rand(10, 128).astype(np.float64)
|
|
544
|
+
b = np.random.rand(8, 128).astype(np.float64)
|
|
545
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
546
|
+
expected = np.asarray(result)
|
|
547
|
+
|
|
548
|
+
sliced = result[2:5]
|
|
549
|
+
assert sliced.shape == (3, 8)
|
|
550
|
+
assert_allclose(np.asarray(sliced), expected[2:5])
|
|
551
|
+
|
|
552
|
+
step_sliced = result[::2]
|
|
553
|
+
assert step_sliced.shape == (5, 8)
|
|
554
|
+
assert_allclose(np.asarray(step_sliced), expected[::2])
|
|
555
|
+
|
|
556
|
+
rev_sliced = result[::-1]
|
|
557
|
+
assert rev_sliced.shape == (10, 8)
|
|
558
|
+
assert_allclose(np.asarray(rev_sliced), expected[::-1])
|
|
559
|
+
|
|
560
|
+
end_sliced = result[-3:]
|
|
561
|
+
assert end_sliced.shape == (3, 8)
|
|
562
|
+
assert_allclose(np.asarray(end_sliced), expected[-3:])
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
566
|
+
def test_distances_tensor_zero_copy_views():
|
|
567
|
+
a = np.random.rand(5, 64).astype(np.float64)
|
|
568
|
+
b = np.random.rand(4, 64).astype(np.float64)
|
|
569
|
+
result = nk.cdist(a, b, metric="sqeuclidean")
|
|
570
|
+
orig_np = np.asarray(result)
|
|
571
|
+
|
|
572
|
+
sliced = result[1:4]
|
|
573
|
+
sliced_np = np.asarray(sliced)
|
|
574
|
+
assert np.shares_memory(orig_np, sliced_np)
|
|
575
|
+
|
|
576
|
+
step_sliced = result[::2]
|
|
577
|
+
step_np = np.asarray(step_sliced)
|
|
578
|
+
assert np.shares_memory(orig_np, step_np)
|
|
579
|
+
|
|
580
|
+
rev_sliced = result[::-1]
|
|
581
|
+
rev_np = np.asarray(rev_sliced)
|
|
582
|
+
assert np.shares_memory(orig_np, rev_np)
|
|
583
|
+
|
|
584
|
+
row = result[0]
|
|
585
|
+
row_np = np.asarray(row)
|
|
586
|
+
assert np.shares_memory(orig_np, row_np)
|
|
587
|
+
|
|
588
|
+
transposed = result.T
|
|
589
|
+
trans_np = np.asarray(transposed)
|
|
590
|
+
assert np.shares_memory(orig_np, trans_np)
|
|
591
|
+
|
|
592
|
+
flat = result.reshape(20)
|
|
593
|
+
flat_np = np.asarray(flat)
|
|
594
|
+
assert np.shares_memory(orig_np, flat_np)
|
|
595
|
+
|
|
596
|
+
trans_reshaped = transposed.reshape(20)
|
|
597
|
+
trans_reshaped_np = np.asarray(trans_reshaped)
|
|
598
|
+
assert not np.shares_memory(orig_np, trans_reshaped_np)
|
|
599
|
+
|
|
600
|
+
chained = result[1:4][1:]
|
|
601
|
+
chained_np = np.asarray(chained)
|
|
602
|
+
assert np.shares_memory(orig_np, chained_np)
|
|
603
|
+
|
|
604
|
+
for _i, row in enumerate(result):
|
|
605
|
+
row_np = np.asarray(row)
|
|
606
|
+
assert np.shares_memory(orig_np, row_np)
|
|
607
|
+
|
|
608
|
+
orig_val = orig_np[2, 0].copy()
|
|
609
|
+
view = result[2]
|
|
610
|
+
view_np = np.asarray(view)
|
|
611
|
+
view_np[0] = 999.0
|
|
612
|
+
assert orig_np[2, 0] == 999.0
|
|
613
|
+
orig_np[2, 0] = orig_val
|
|
614
|
+
|
|
615
|
+
copied = result.copy()
|
|
616
|
+
copied_np = np.asarray(copied)
|
|
617
|
+
assert not np.shares_memory(orig_np, copied_np)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@pytest.mark.parametrize(
|
|
621
|
+
"dtype",
|
|
622
|
+
[
|
|
623
|
+
pytest.param("float64", id="f64"),
|
|
624
|
+
pytest.param("float32", id="f32"),
|
|
625
|
+
pytest.param("int8", id="i8"),
|
|
626
|
+
pytest.param("int32", id="i32"),
|
|
627
|
+
],
|
|
628
|
+
)
|
|
629
|
+
@pytest.mark.parametrize(
|
|
630
|
+
"shape",
|
|
631
|
+
[pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4"), pytest.param((2, 3, 4), id="3d-2x3x4")],
|
|
632
|
+
)
|
|
633
|
+
def test_ndarray_zeros(dtype: str, shape):
|
|
634
|
+
arr = nk.zeros(shape, dtype=dtype)
|
|
635
|
+
assert arr.shape == shape
|
|
636
|
+
assert arr.dtype == dtype
|
|
637
|
+
flat = arr.flatten()
|
|
638
|
+
for i in range(arr.size):
|
|
639
|
+
assert float(flat[i]) == 0
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@pytest.mark.parametrize(
|
|
643
|
+
"dtype",
|
|
644
|
+
[
|
|
645
|
+
pytest.param("float64", id="f64"),
|
|
646
|
+
pytest.param("float32", id="f32"),
|
|
647
|
+
pytest.param("int8", id="i8"),
|
|
648
|
+
pytest.param("int32", id="i32"),
|
|
649
|
+
],
|
|
650
|
+
)
|
|
651
|
+
@pytest.mark.parametrize(
|
|
652
|
+
"shape",
|
|
653
|
+
[pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4"), pytest.param((2, 3, 4), id="3d-2x3x4")],
|
|
654
|
+
)
|
|
655
|
+
def test_ndarray_ones(dtype: str, shape):
|
|
656
|
+
arr = nk.ones(shape, dtype=dtype)
|
|
657
|
+
assert arr.shape == shape
|
|
658
|
+
assert arr.dtype == dtype
|
|
659
|
+
flat = arr.flatten()
|
|
660
|
+
for i in range(arr.size):
|
|
661
|
+
assert float(flat[i]) == 1
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
@pytest.mark.parametrize(
|
|
665
|
+
"dtype,fill_value",
|
|
666
|
+
[
|
|
667
|
+
pytest.param("float64", 3.14, id="f64-pi"),
|
|
668
|
+
pytest.param("float32", -2.5, id="f32-neg"),
|
|
669
|
+
pytest.param("int8", 42, id="i8-42"),
|
|
670
|
+
pytest.param("int32", -100, id="i32-neg"),
|
|
671
|
+
],
|
|
672
|
+
)
|
|
673
|
+
@pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4")])
|
|
674
|
+
def test_ndarray_full(dtype: str, fill_value, shape):
|
|
675
|
+
arr = nk.full(shape, fill_value, dtype=dtype)
|
|
676
|
+
assert arr.shape == shape
|
|
677
|
+
assert arr.dtype == dtype
|
|
678
|
+
flat = arr.flatten()
|
|
679
|
+
for i in range(arr.size):
|
|
680
|
+
val = float(flat[i])
|
|
681
|
+
if dtype.startswith("float"):
|
|
682
|
+
assert abs(val - fill_value) < 1e-5
|
|
683
|
+
else:
|
|
684
|
+
assert int(val) == fill_value
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
688
|
+
@pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4")])
|
|
689
|
+
def test_ndarray_empty(dtype: str, shape):
|
|
690
|
+
arr = nk.empty(shape, dtype=dtype)
|
|
691
|
+
assert arr.shape == shape
|
|
692
|
+
assert arr.dtype == dtype
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
@pytest.mark.parametrize(
|
|
696
|
+
"dtype",
|
|
697
|
+
[
|
|
698
|
+
pytest.param("float32", id="f32"),
|
|
699
|
+
pytest.param("float64", id="f64"),
|
|
700
|
+
pytest.param("int8", id="i8"),
|
|
701
|
+
pytest.param("int32", id="i32"),
|
|
702
|
+
],
|
|
703
|
+
)
|
|
704
|
+
@pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((3, 4), id="2d-3x4")])
|
|
705
|
+
def test_ndarray_iota(dtype: str, shape):
|
|
706
|
+
seed = 5
|
|
707
|
+
arr = nk.iota(shape, seed, dtype=dtype)
|
|
708
|
+
assert arr.shape == shape
|
|
709
|
+
assert arr.dtype == dtype
|
|
710
|
+
flat = arr.flatten()
|
|
711
|
+
assert float(flat[0]) == seed
|
|
712
|
+
assert float(flat[1]) == seed + 1
|
|
713
|
+
assert float(flat[2]) == seed + 2
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@pytest.mark.parametrize(
|
|
717
|
+
"dtype",
|
|
718
|
+
[
|
|
719
|
+
pytest.param("float32", id="f32"),
|
|
720
|
+
pytest.param("float64", id="f64"),
|
|
721
|
+
pytest.param("int8", id="i8"),
|
|
722
|
+
pytest.param("int32", id="i32"),
|
|
723
|
+
],
|
|
724
|
+
)
|
|
725
|
+
@pytest.mark.parametrize("n", [pytest.param(4, id="4x4"), pytest.param(8, id="8x8")])
|
|
726
|
+
def test_ndarray_diagonal(dtype: str, n):
|
|
727
|
+
seed = 3
|
|
728
|
+
arr = nk.diagonal(n, seed, dtype=dtype)
|
|
729
|
+
assert arr.shape == (n, n)
|
|
730
|
+
assert arr.dtype == dtype
|
|
731
|
+
for i in range(n):
|
|
732
|
+
assert float(arr[i, i]) == seed
|
|
733
|
+
if i + 1 < n:
|
|
734
|
+
assert float(arr[i, i + 1]) == 0
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
@pytest.mark.parametrize(
|
|
738
|
+
"dtype",
|
|
739
|
+
[
|
|
740
|
+
pytest.param("float32", id="f32"),
|
|
741
|
+
pytest.param("float64", id="f64"),
|
|
742
|
+
pytest.param("int8", id="i8"),
|
|
743
|
+
pytest.param("int32", id="i32"),
|
|
744
|
+
],
|
|
745
|
+
)
|
|
746
|
+
@pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((3, 4), id="2d-3x4")])
|
|
747
|
+
def test_ndarray_hash(dtype: str, shape):
|
|
748
|
+
a = nk.hash(shape, seed=42, dtype=dtype)
|
|
749
|
+
b = nk.hash(shape, seed=42, dtype=dtype)
|
|
750
|
+
assert a.shape == shape
|
|
751
|
+
assert a.dtype == dtype
|
|
752
|
+
# Determinism: same seed → same data
|
|
753
|
+
flat_a = a.flatten()
|
|
754
|
+
flat_b = b.flatten()
|
|
755
|
+
total = 1
|
|
756
|
+
for d in shape:
|
|
757
|
+
total *= d
|
|
758
|
+
for i in range(total):
|
|
759
|
+
assert float(flat_a[i]) == float(flat_b[i])
|
|
760
|
+
# Different seed → different data
|
|
761
|
+
c = nk.hash(shape, seed=99, dtype=dtype)
|
|
762
|
+
flat_c = c.flatten()
|
|
763
|
+
differs = any(float(flat_a[i]) != float(flat_c[i]) for i in range(total))
|
|
764
|
+
assert differs, "Different seeds should produce different data"
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
768
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
769
|
+
@pytest.mark.parametrize(
|
|
770
|
+
"shape",
|
|
771
|
+
[pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10"), pytest.param((4, 5, 5), id="3d-4x5x5")],
|
|
772
|
+
)
|
|
773
|
+
def test_ndarray_sum_float(dtype: str, shape):
|
|
774
|
+
np_arr = np.random.randn(*shape).astype(dtype)
|
|
775
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
776
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["sum"]
|
|
777
|
+
expected = baseline_kernel(np_arr)
|
|
778
|
+
result = simd_kernel(nk_arr)
|
|
779
|
+
atol, rtol = tolerances_for_dtype(dtype)
|
|
780
|
+
assert_allclose(result, expected, rtol=rtol, atol=atol)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
784
|
+
@pytest.mark.parametrize("dtype", [pytest.param("int8", id="i8"), pytest.param("int32", id="i32")])
|
|
785
|
+
@pytest.mark.parametrize(
|
|
786
|
+
"shape",
|
|
787
|
+
[pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10"), pytest.param((4, 5, 5), id="3d-4x5x5")],
|
|
788
|
+
)
|
|
789
|
+
def test_ndarray_sum_integer(dtype: str, shape):
|
|
790
|
+
nk_dtype_conversion_info = np.iinfo(np.dtype(dtype))
|
|
791
|
+
np_arr = np.random.randint(
|
|
792
|
+
nk_dtype_conversion_info.min // 2, nk_dtype_conversion_info.max // 2, size=shape, dtype=dtype
|
|
793
|
+
)
|
|
794
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
795
|
+
assert nk_arr.sum() == np_arr.sum()
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
799
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
800
|
+
@pytest.mark.parametrize("shape", [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10")])
|
|
801
|
+
def test_ndarray_min_max_float(dtype: str, shape):
|
|
802
|
+
np_arr = np.random.randn(*shape).astype(dtype)
|
|
803
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
804
|
+
atol, rtol = tolerances_for_dtype(dtype)
|
|
805
|
+
for op in ("min", "max"):
|
|
806
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
|
|
807
|
+
assert_allclose(simd_kernel(nk_arr), baseline_kernel(np_arr), rtol=rtol, atol=atol)
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
811
|
+
@pytest.mark.parametrize("dtype", [pytest.param("int8", id="i8"), pytest.param("int32", id="i32")])
|
|
812
|
+
@pytest.mark.parametrize("shape", [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10")])
|
|
813
|
+
def test_ndarray_min_max_integer(dtype: str, shape):
|
|
814
|
+
nk_dtype_conversion_info = np.iinfo(np.dtype(dtype))
|
|
815
|
+
np_arr = np.random.randint(
|
|
816
|
+
nk_dtype_conversion_info.min // 2, nk_dtype_conversion_info.max // 2, size=shape, dtype=dtype
|
|
817
|
+
)
|
|
818
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
819
|
+
assert nk_arr.min() == np_arr.min()
|
|
820
|
+
assert nk_arr.max() == np_arr.max()
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
824
|
+
@pytest.mark.parametrize(
|
|
825
|
+
"dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32"), pytest.param("int32", id="i32")]
|
|
826
|
+
)
|
|
827
|
+
@pytest.mark.parametrize("shape", [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10")])
|
|
828
|
+
def test_ndarray_argmin_argmax_methods(dtype: str, shape):
|
|
829
|
+
if dtype.startswith("float"):
|
|
830
|
+
np_arr = np.random.randn(*shape).astype(dtype)
|
|
831
|
+
else:
|
|
832
|
+
nk_dtype_conversion_info = np.iinfo(np.dtype(dtype))
|
|
833
|
+
np_arr = np.random.randint(
|
|
834
|
+
nk_dtype_conversion_info.min // 2, nk_dtype_conversion_info.max // 2, size=shape, dtype=dtype
|
|
835
|
+
)
|
|
836
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
837
|
+
|
|
838
|
+
for op in ("argmin", "argmax"):
|
|
839
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
|
|
840
|
+
assert simd_kernel(nk_arr) == baseline_kernel(np_arr)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
844
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
845
|
+
def test_ndarray_add_float(dtype: str):
|
|
846
|
+
shape = (20,)
|
|
847
|
+
np_a = np.random.randn(*shape).astype(dtype)
|
|
848
|
+
np_b = np.random.randn(*shape).astype(dtype)
|
|
849
|
+
nk_a = make_nk(np_a, dtype)
|
|
850
|
+
nk_b = make_nk(np_b, dtype)
|
|
851
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["add"]
|
|
852
|
+
result_np = np.asarray(simd_kernel(nk_a, nk_b))
|
|
853
|
+
assert_allclose(result_np, baseline_kernel(np_a, np_b))
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
857
|
+
def test_ndarray_add_integer():
|
|
858
|
+
shape = (20,)
|
|
859
|
+
np_a = np.random.randint(-50, 50, size=shape, dtype="int32")
|
|
860
|
+
np_b = np.random.randint(-50, 50, size=shape, dtype="int32")
|
|
861
|
+
nk_a = make_nk(np_a, "int32")
|
|
862
|
+
nk_b = make_nk(np_b, "int32")
|
|
863
|
+
np.testing.assert_array_equal(np.asarray(nk_a + nk_b), np_a + np_b)
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
867
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
868
|
+
def test_ndarray_subtract_float(dtype: str):
|
|
869
|
+
shape = (20,)
|
|
870
|
+
np_a = np.random.randn(*shape).astype(dtype)
|
|
871
|
+
np_b = np.random.randn(*shape).astype(dtype)
|
|
872
|
+
nk_a = make_nk(np_a, dtype)
|
|
873
|
+
nk_b = make_nk(np_b, dtype)
|
|
874
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["subtract"]
|
|
875
|
+
result_np = np.asarray(simd_kernel(nk_a, nk_b))
|
|
876
|
+
assert_allclose(result_np, baseline_kernel(np_a, np_b))
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
880
|
+
def test_ndarray_subtract_integer():
|
|
881
|
+
shape = (20,)
|
|
882
|
+
np_a = np.random.randint(-50, 50, size=shape, dtype="int32")
|
|
883
|
+
np_b = np.random.randint(-50, 50, size=shape, dtype="int32")
|
|
884
|
+
nk_a = make_nk(np_a, "int32")
|
|
885
|
+
nk_b = make_nk(np_b, "int32")
|
|
886
|
+
np.testing.assert_array_equal(np.asarray(nk_a - nk_b), np_a - np_b)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
890
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
891
|
+
def test_ndarray_multiply_float(dtype: str):
|
|
892
|
+
shape = (20,)
|
|
893
|
+
np_a = np.random.randn(*shape).astype(dtype)
|
|
894
|
+
np_b = np.random.randn(*shape).astype(dtype)
|
|
895
|
+
nk_a = make_nk(np_a, dtype)
|
|
896
|
+
nk_b = make_nk(np_b, dtype)
|
|
897
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["multiply"]
|
|
898
|
+
result_np = np.asarray(simd_kernel(nk_a, nk_b))
|
|
899
|
+
assert_allclose(result_np, baseline_kernel(np_a, np_b))
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
903
|
+
def test_ndarray_multiply_integer():
|
|
904
|
+
shape = (20,)
|
|
905
|
+
np_a = np.random.randint(-10, 10, size=shape, dtype="int32")
|
|
906
|
+
np_b = np.random.randint(-10, 10, size=shape, dtype="int32")
|
|
907
|
+
nk_a = make_nk(np_a, "int32")
|
|
908
|
+
nk_b = make_nk(np_b, "int32")
|
|
909
|
+
np.testing.assert_array_equal(np.asarray(nk_a * nk_b), np_a * np_b)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
913
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
914
|
+
def test_ndarray_unary_float(dtype: str):
|
|
915
|
+
shape = (20,)
|
|
916
|
+
np_a = np.random.randn(*shape).astype(dtype)
|
|
917
|
+
nk_a = make_nk(np_a, dtype)
|
|
918
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["negate"]
|
|
919
|
+
result_neg_np = np.asarray(simd_kernel(nk_a))
|
|
920
|
+
assert_allclose(result_neg_np, baseline_kernel(np_a))
|
|
921
|
+
np.testing.assert_array_equal(np.asarray(+nk_a), np_a)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
925
|
+
def test_ndarray_unary_integer():
|
|
926
|
+
shape = (20,)
|
|
927
|
+
np_a = np.random.randint(-50, 50, size=shape, dtype="int32")
|
|
928
|
+
nk_a = make_nk(np_a, "int32")
|
|
929
|
+
np.testing.assert_array_equal(np.asarray(-nk_a), -np_a)
|
|
930
|
+
np.testing.assert_array_equal(np.asarray(+nk_a), np_a)
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
934
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
935
|
+
def test_reduction_on_strided_array(dtype: str):
|
|
936
|
+
np_arr = np.random.randn(10, 10).astype(dtype)
|
|
937
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
938
|
+
|
|
939
|
+
np_strided = np_arr[::2]
|
|
940
|
+
nk_strided = nk_arr[::2]
|
|
941
|
+
|
|
942
|
+
atol, rtol = tolerances_for_dtype(dtype)
|
|
943
|
+
for op in ("sum", "min", "max"):
|
|
944
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
|
|
945
|
+
assert_allclose(simd_kernel(nk_strided), baseline_kernel(np_strided), rtol=rtol, atol=atol)
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
949
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
950
|
+
def test_reduction_on_transposed_array(dtype: str):
|
|
951
|
+
np_arr = np.random.randn(5, 8).astype(dtype)
|
|
952
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
953
|
+
|
|
954
|
+
np_t = np_arr.T
|
|
955
|
+
nk_t = nk_arr.T
|
|
956
|
+
|
|
957
|
+
atol, rtol = tolerances_for_dtype(dtype)
|
|
958
|
+
for op in ("sum", "min", "max"):
|
|
959
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
|
|
960
|
+
assert_allclose(simd_kernel(nk_t), baseline_kernel(np_t), rtol=rtol, atol=atol)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
964
|
+
@pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
|
|
965
|
+
def test_reduction_on_subview(dtype: str):
|
|
966
|
+
np_arr = np.random.randn(20, 20).astype(dtype)
|
|
967
|
+
nk_arr = make_nk(np_arr, dtype)
|
|
968
|
+
|
|
969
|
+
np_sub = np_arr[5:15, 5:15]
|
|
970
|
+
nk_sub = nk_arr[5:15, 5:15]
|
|
971
|
+
|
|
972
|
+
atol, rtol = tolerances_for_dtype(dtype)
|
|
973
|
+
for op in ("sum", "min", "max"):
|
|
974
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
|
|
975
|
+
assert_allclose(simd_kernel(nk_sub), baseline_kernel(np_sub), rtol=rtol, atol=atol)
|
|
976
|
+
for op in ("argmin", "argmax"):
|
|
977
|
+
baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
|
|
978
|
+
assert simd_kernel(nk_sub) == baseline_kernel(np_sub)
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def test_bfloat16_scalar_creation():
|
|
982
|
+
bf = nk.bfloat16(3.14159)
|
|
983
|
+
assert isinstance(bf, nk.bfloat16)
|
|
984
|
+
assert abs(float(bf) - 3.14159) < 0.01
|
|
985
|
+
assert int(bf) == 3
|
|
986
|
+
assert repr(bf) # smoke-test repr doesn't crash
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def test_bfloat16_scalar_arithmetic():
|
|
990
|
+
a = nk.bfloat16(1.5)
|
|
991
|
+
b = nk.bfloat16(2.5)
|
|
992
|
+
|
|
993
|
+
result = a + b
|
|
994
|
+
assert isinstance(result, nk.bfloat16)
|
|
995
|
+
assert float(result) == 4.0
|
|
996
|
+
|
|
997
|
+
result = b - a
|
|
998
|
+
assert isinstance(result, nk.bfloat16)
|
|
999
|
+
assert float(result) == 1.0
|
|
1000
|
+
|
|
1001
|
+
result = a * b
|
|
1002
|
+
assert isinstance(result, nk.bfloat16)
|
|
1003
|
+
assert float(result) == 3.75
|
|
1004
|
+
|
|
1005
|
+
result = b / a
|
|
1006
|
+
assert isinstance(result, nk.bfloat16)
|
|
1007
|
+
assert abs(float(result) - 1.6666) < 0.01
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def test_bfloat16_scalar_unary():
|
|
1011
|
+
a = nk.bfloat16(1.5)
|
|
1012
|
+
assert float(-a) == -1.5
|
|
1013
|
+
assert float(+a) == 1.5
|
|
1014
|
+
assert float(abs(nk.bfloat16(-1.5))) == 1.5
|
|
1015
|
+
assert bool(a)
|
|
1016
|
+
assert not bool(nk.bfloat16(0.0))
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def test_bfloat16_scalar_comparison():
|
|
1020
|
+
a = nk.bfloat16(1.5)
|
|
1021
|
+
b = nk.bfloat16(2.5)
|
|
1022
|
+
|
|
1023
|
+
assert a < b
|
|
1024
|
+
assert a <= b
|
|
1025
|
+
assert a <= a
|
|
1026
|
+
assert b > a
|
|
1027
|
+
assert b >= a
|
|
1028
|
+
assert a == a
|
|
1029
|
+
assert a != b
|
|
1030
|
+
|
|
1031
|
+
assert a == 1.5
|
|
1032
|
+
assert a < 2.0
|
|
1033
|
+
assert a > 1.0
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def test_bfloat16_scalar_hash():
|
|
1037
|
+
a = nk.bfloat16(1.5)
|
|
1038
|
+
b = nk.bfloat16(1.5)
|
|
1039
|
+
s = {a, b}
|
|
1040
|
+
assert len(s) == 1
|
|
1041
|
+
d = {a: "value"}
|
|
1042
|
+
assert d[b] == "value"
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def test_float8_e4m3_scalar():
|
|
1046
|
+
f8 = nk.float8_e4m3(1.5)
|
|
1047
|
+
assert isinstance(f8, nk.float8_e4m3)
|
|
1048
|
+
assert float(f8) == 1.5
|
|
1049
|
+
assert int(f8) == 1
|
|
1050
|
+
assert "float8_e4m3" in repr(f8)
|
|
1051
|
+
assert float(-f8) == -1.5
|
|
1052
|
+
assert bool(f8)
|
|
1053
|
+
assert not bool(nk.float8_e4m3(0.0))
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def test_float8_e4m3_scalar_arithmetic():
|
|
1057
|
+
a = nk.float8_e4m3(1.5)
|
|
1058
|
+
b = nk.float8_e4m3(2.0)
|
|
1059
|
+
|
|
1060
|
+
result = a + b
|
|
1061
|
+
assert isinstance(result, nk.float8_e4m3)
|
|
1062
|
+
assert abs(float(result) - 3.5) < 0.5
|
|
1063
|
+
|
|
1064
|
+
result = b - a
|
|
1065
|
+
assert isinstance(result, nk.float8_e4m3)
|
|
1066
|
+
assert abs(float(result) - 0.5) < 0.5
|
|
1067
|
+
|
|
1068
|
+
result = a * b
|
|
1069
|
+
assert isinstance(result, nk.float8_e4m3)
|
|
1070
|
+
assert abs(float(result) - 3.0) < 0.5
|
|
1071
|
+
|
|
1072
|
+
result = b / a
|
|
1073
|
+
assert isinstance(result, nk.float8_e4m3)
|
|
1074
|
+
assert abs(float(result) - 1.33) < 0.5
|
|
1075
|
+
|
|
1076
|
+
assert float(+a) == float(a)
|
|
1077
|
+
assert float(abs(nk.float8_e4m3(-1.5))) == 1.5
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def test_float8_e5m2_scalar():
|
|
1081
|
+
f8 = nk.float8_e5m2(1.5)
|
|
1082
|
+
assert isinstance(f8, nk.float8_e5m2)
|
|
1083
|
+
assert float(f8) == 1.5
|
|
1084
|
+
assert int(f8) == 1
|
|
1085
|
+
assert "float8_e5m2" in repr(f8)
|
|
1086
|
+
assert float(-f8) == -1.5
|
|
1087
|
+
assert bool(f8)
|
|
1088
|
+
assert not bool(nk.float8_e5m2(0.0))
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
def test_float8_e5m2_scalar_arithmetic():
|
|
1092
|
+
a = nk.float8_e5m2(1.5)
|
|
1093
|
+
b = nk.float8_e5m2(2.0)
|
|
1094
|
+
|
|
1095
|
+
result = a + b
|
|
1096
|
+
assert isinstance(result, nk.float8_e5m2)
|
|
1097
|
+
assert abs(float(result) - 3.5) < 0.5
|
|
1098
|
+
|
|
1099
|
+
result = b - a
|
|
1100
|
+
assert isinstance(result, nk.float8_e5m2)
|
|
1101
|
+
assert abs(float(result) - 0.5) < 0.5
|
|
1102
|
+
|
|
1103
|
+
result = a * b
|
|
1104
|
+
assert isinstance(result, nk.float8_e5m2)
|
|
1105
|
+
assert abs(float(result) - 3.0) < 0.5
|
|
1106
|
+
|
|
1107
|
+
result = b / a
|
|
1108
|
+
assert isinstance(result, nk.float8_e5m2)
|
|
1109
|
+
assert abs(float(result) - 1.33) < 0.5
|
|
1110
|
+
|
|
1111
|
+
assert float(+a) == float(a)
|
|
1112
|
+
assert float(abs(nk.float8_e5m2(-1.5))) == 1.5
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
|
|
1116
|
+
def test_bfloat16_vs_ml_dtypes():
|
|
1117
|
+
test_values = [0.0, 1.0, -1.0, 3.14159, 100.0, 0.001, -65504.0]
|
|
1118
|
+
for val in test_values:
|
|
1119
|
+
nk_bf16 = nk.bfloat16(val)
|
|
1120
|
+
ml_bf16 = ml_dtypes.bfloat16(val)
|
|
1121
|
+
assert float(nk_bf16) == float(ml_bf16), f"Mismatch for {val}: nk={float(nk_bf16)}, ml={float(ml_bf16)}"
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
|
|
1125
|
+
def test_float8_e4m3_vs_ml_dtypes():
|
|
1126
|
+
test_values = [0.0, 1.0, -1.0, 1.5, 2.0, 0.5]
|
|
1127
|
+
for val in test_values:
|
|
1128
|
+
nk_f8 = nk.float8_e4m3(val)
|
|
1129
|
+
ml_f8 = ml_dtypes.float8_e4m3fn(val)
|
|
1130
|
+
assert float(nk_f8) == float(ml_f8), f"Mismatch for {val}: nk={float(nk_f8)}, ml={float(ml_f8)}"
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
|
|
1134
|
+
def test_float8_e5m2_vs_ml_dtypes():
|
|
1135
|
+
test_values = [0.0, 1.0, -1.0, 1.5, 2.0, 0.5]
|
|
1136
|
+
for val in test_values:
|
|
1137
|
+
nk_f8 = nk.float8_e5m2(val)
|
|
1138
|
+
ml_f8 = ml_dtypes.float8_e5m2(val)
|
|
1139
|
+
assert float(nk_f8) == float(ml_f8), f"Mismatch for {val}: nk={float(nk_f8)}, ml={float(ml_f8)}"
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1143
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
|
|
1144
|
+
@pytest.mark.parametrize(
|
|
1145
|
+
"ml_dtype,nk_name",
|
|
1146
|
+
[
|
|
1147
|
+
("bfloat16", "bfloat16"),
|
|
1148
|
+
("float8_e4m3fn", "e4m3"),
|
|
1149
|
+
("float8_e5m2", "e5m2"),
|
|
1150
|
+
("float6_e2m3fn", "e2m3"),
|
|
1151
|
+
("float6_e3m2fn", "e3m2"),
|
|
1152
|
+
],
|
|
1153
|
+
)
|
|
1154
|
+
def test_ml_dtypes_array_to_tensor(ml_dtype, nk_name):
|
|
1155
|
+
"""Verify that ml_dtypes arrays can be consumed as nk.Tensor via __array_interface__."""
|
|
1156
|
+
dt = getattr(ml_dtypes, ml_dtype)
|
|
1157
|
+
a_f32 = np.random.randn(16).astype(np.float32).clip(-1, 1)
|
|
1158
|
+
a_ml = a_f32.astype(dt)
|
|
1159
|
+
# 1D
|
|
1160
|
+
t = nk.Tensor(a_ml)
|
|
1161
|
+
assert t.dtype == nk_name
|
|
1162
|
+
assert t.shape == (16,)
|
|
1163
|
+
# 2D
|
|
1164
|
+
a_2d = np.random.randn(4, 8).astype(np.float32).clip(-1, 1).astype(dt)
|
|
1165
|
+
t2 = nk.Tensor(a_2d)
|
|
1166
|
+
assert t2.dtype == nk_name
|
|
1167
|
+
assert t2.shape == (4, 8)
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1171
|
+
@pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
|
|
1172
|
+
@pytest.mark.parametrize(
|
|
1173
|
+
"ml_dtype",
|
|
1174
|
+
["float8_e4m3fnuz", "float8_e5m2fnuz", "float8_e4m3b11fnuz", "float8_e8m0fnu"],
|
|
1175
|
+
)
|
|
1176
|
+
def test_ml_dtypes_incompatible_rejected(ml_dtype):
|
|
1177
|
+
"""Verify that fnuz/b11fnuz/e8m0fnu types are rejected, not silently misinterpreted."""
|
|
1178
|
+
dt = getattr(ml_dtypes, ml_dtype, None)
|
|
1179
|
+
if dt is None:
|
|
1180
|
+
pytest.skip(f"ml_dtypes.{ml_dtype} not available in this version")
|
|
1181
|
+
a = np.array([0.5], dtype=dt)
|
|
1182
|
+
with pytest.raises((TypeError, ValueError)):
|
|
1183
|
+
nk.Tensor(a)
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1187
|
+
@pytest.mark.parametrize(
|
|
1188
|
+
"scalar_type,name",
|
|
1189
|
+
[
|
|
1190
|
+
(nk.bfloat16, "bfloat16"),
|
|
1191
|
+
(nk.float8_e4m3, "float8_e4m3"),
|
|
1192
|
+
(nk.float8_e5m2, "float8_e5m2"),
|
|
1193
|
+
],
|
|
1194
|
+
)
|
|
1195
|
+
def test_numpy_array_with_nk_dtype(scalar_type, name):
|
|
1196
|
+
"""Verify that nk scalar types can be used as NumPy dtype specifiers."""
|
|
1197
|
+
if not hasattr(scalar_type, "dtype"):
|
|
1198
|
+
pytest.skip("NumPy dtype registration not available")
|
|
1199
|
+
arr = np.array([1.0, 2.0, 3.0], dtype=scalar_type)
|
|
1200
|
+
assert abs(float(arr[0]) - 1.0) < 0.1
|
|
1201
|
+
assert abs(float(arr[1]) - 2.0) < 0.1
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1205
|
+
def test_nk_dtype_numpy_roundtrip():
|
|
1206
|
+
"""Verify roundtrip: np.array(dtype=nk.bfloat16) → float32 → bfloat16 preserves values."""
|
|
1207
|
+
if not hasattr(nk.bfloat16, "dtype"):
|
|
1208
|
+
pytest.skip("NumPy dtype registration not available")
|
|
1209
|
+
vals = [0.0, 1.0, -1.0, 0.5, 3.14]
|
|
1210
|
+
arr = np.array(vals, dtype=nk.bfloat16)
|
|
1211
|
+
# Cast to float32 and back to verify the registered cast functions work.
|
|
1212
|
+
f32 = arr.astype(np.float32)
|
|
1213
|
+
back = f32.astype(nk.bfloat16)
|
|
1214
|
+
for i in range(len(vals)):
|
|
1215
|
+
assert float(arr[i]) == float(back[i])
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1219
|
+
def test_dots_packed_row_range():
|
|
1220
|
+
"""Test dots_packed with start_row/end_row splits produce the same result."""
|
|
1221
|
+
height, depth, width = 100, 64, 50
|
|
1222
|
+
left_matrix = np.random.randn(height, depth).astype(np.float32)
|
|
1223
|
+
right_matrix = np.ascontiguousarray(np.random.randn(width, depth).astype(np.float32))
|
|
1224
|
+
right_packed = nk.dots_pack(right_matrix, dtype="float32")
|
|
1225
|
+
|
|
1226
|
+
reference = np.array(nk.dots_packed(left_matrix, right_packed))
|
|
1227
|
+
|
|
1228
|
+
output = nk.zeros((height, width), dtype="float64")
|
|
1229
|
+
nk.dots_packed(left_matrix, right_packed, out=output, start_row=0, end_row=50)
|
|
1230
|
+
nk.dots_packed(left_matrix, right_packed, out=output, start_row=50, end_row=100)
|
|
1231
|
+
|
|
1232
|
+
assert_allclose(np.array(output), reference, err_msg="Row-range split differs from full computation")
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1236
|
+
def test_dots_symmetric_row_range():
|
|
1237
|
+
"""Test dots_symmetric with start_row/end_row.
|
|
1238
|
+
|
|
1239
|
+
Only the upper triangle of the output is guaranteed to be initialized,
|
|
1240
|
+
so we compare only the upper-triangle entries that fall within each row range.
|
|
1241
|
+
"""
|
|
1242
|
+
count, depth = 64, 32
|
|
1243
|
+
vectors = np.random.randn(count, depth).astype(np.float32)
|
|
1244
|
+
|
|
1245
|
+
reference = np.array(nk.dots_symmetric(vectors))
|
|
1246
|
+
mask = np.triu(np.ones((count, count), dtype=bool))
|
|
1247
|
+
|
|
1248
|
+
output = nk.zeros((count, count), dtype="float64")
|
|
1249
|
+
nk.dots_symmetric(vectors, out=output, start_row=0, end_row=count)
|
|
1250
|
+
|
|
1251
|
+
assert_allclose(np.array(output)[mask], reference[mask], err_msg="Full-range dots_symmetric differs from default")
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1255
|
+
@pytest.mark.parametrize("height", [63, 64, 129])
|
|
1256
|
+
def test_dots_packed_threads(height):
|
|
1257
|
+
"""Verify OpenMP-parallel dots_packed matches the serial path across tile boundaries.
|
|
1258
|
+
|
|
1259
|
+
The packed row tile is 64, so sizes straddling one and two tiles exercise
|
|
1260
|
+
both the whole-tile and tail-chunk branches of the parallel loop.
|
|
1261
|
+
"""
|
|
1262
|
+
depth, width = 64, 32
|
|
1263
|
+
left_matrix = np.random.randn(height, depth).astype(np.float32)
|
|
1264
|
+
right_matrix = np.ascontiguousarray(np.random.randn(width, depth).astype(np.float32))
|
|
1265
|
+
right_packed = nk.dots_pack(right_matrix, dtype="float32")
|
|
1266
|
+
|
|
1267
|
+
serial = np.array(nk.dots_packed(left_matrix, right_packed, threads=1))
|
|
1268
|
+
parallel = np.array(nk.dots_packed(left_matrix, right_packed, threads=4))
|
|
1269
|
+
assert_allclose(parallel, serial, err_msg=f"threads=4 diverges from threads=1 at height={height}")
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1273
|
+
@pytest.mark.parametrize("count", [31, 32, 65])
|
|
1274
|
+
def test_dots_symmetric_threads(count):
|
|
1275
|
+
"""Verify OpenMP-parallel dots_symmetric matches the serial path across tile boundaries.
|
|
1276
|
+
|
|
1277
|
+
The symmetric row tile is 32; only the upper triangle is guaranteed written.
|
|
1278
|
+
"""
|
|
1279
|
+
depth = 32
|
|
1280
|
+
vectors = np.random.randn(count, depth).astype(np.float32)
|
|
1281
|
+
mask = np.triu(np.ones((count, count), dtype=bool))
|
|
1282
|
+
|
|
1283
|
+
serial = np.array(nk.dots_symmetric(vectors, threads=1))
|
|
1284
|
+
parallel = np.array(nk.dots_symmetric(vectors, threads=4))
|
|
1285
|
+
assert_allclose(parallel[mask], serial[mask], err_msg=f"threads=4 diverges from threads=1 at count={count}")
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
def _skip_unless_free_threaded():
|
|
1289
|
+
version = sys.version_info
|
|
1290
|
+
if version.major == 3 and version.minor >= 13:
|
|
1291
|
+
is_free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
|
1292
|
+
if not is_free_threaded:
|
|
1293
|
+
pytest.skip("Uses non-free-threaded Python, skipping GIL-related tests")
|
|
1294
|
+
if sys._is_gil_enabled():
|
|
1295
|
+
pytest.skip("GIL is enabled, skipping GIL-related tests")
|
|
1296
|
+
else:
|
|
1297
|
+
pytest.skip("Python < 3.13t, skipping GIL-related tests")
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1301
|
+
def test_gil_free_threading():
|
|
1302
|
+
"""Test NumKong in Python 3.13t free-threaded mode if available."""
|
|
1303
|
+
_skip_unless_free_threaded()
|
|
1304
|
+
|
|
1305
|
+
num_threads = multiprocessing.cpu_count()
|
|
1306
|
+
vectors_a = np.random.rand(32 * 1024 * num_threads, 1024).astype(np.float32)
|
|
1307
|
+
vectors_b = np.random.rand(32 * 1024 * num_threads, 1024).astype(np.float32)
|
|
1308
|
+
distances = np.zeros(vectors_a.shape[0], dtype=np.float32)
|
|
1309
|
+
|
|
1310
|
+
def compute_batch(start_idx, end_idx) -> float:
|
|
1311
|
+
slice_a = vectors_a[start_idx:end_idx]
|
|
1312
|
+
slice_b = vectors_b[start_idx:end_idx]
|
|
1313
|
+
slice_distances = distances[start_idx:end_idx]
|
|
1314
|
+
nk.angular(slice_a, slice_b, out=slice_distances)
|
|
1315
|
+
return sum(slice_distances)
|
|
1316
|
+
|
|
1317
|
+
def compute_with_threads(threads: int) -> float:
|
|
1318
|
+
chunk_size = len(vectors_a) // threads
|
|
1319
|
+
futures = []
|
|
1320
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
|
|
1321
|
+
for i in range(threads):
|
|
1322
|
+
start_idx = i * chunk_size
|
|
1323
|
+
end_idx = (i + 1) * chunk_size if i < threads - 1 else len(vectors_a)
|
|
1324
|
+
futures.append(executor.submit(compute_batch, start_idx, end_idx))
|
|
1325
|
+
total_sum = 0.0
|
|
1326
|
+
for future in concurrent.futures.as_completed(futures):
|
|
1327
|
+
total_sum += future.result()
|
|
1328
|
+
return total_sum
|
|
1329
|
+
|
|
1330
|
+
start_time = time.time()
|
|
1331
|
+
baseline_sum = compute_with_threads(2)
|
|
1332
|
+
end_time = time.time()
|
|
1333
|
+
baseline_duration = end_time - start_time
|
|
1334
|
+
|
|
1335
|
+
start_time = time.time()
|
|
1336
|
+
multi_sum = compute_with_threads(num_threads)
|
|
1337
|
+
end_time = time.time()
|
|
1338
|
+
multi_duration = end_time - start_time
|
|
1339
|
+
|
|
1340
|
+
assert np.allclose(
|
|
1341
|
+
baseline_sum, multi_sum, atol=NK_ATOL, rtol=NK_RTOL
|
|
1342
|
+
), f"Results differ: baseline {baseline_sum} vs multi-threaded {multi_sum}"
|
|
1343
|
+
|
|
1344
|
+
if baseline_duration < multi_duration:
|
|
1345
|
+
warnings.warn(
|
|
1346
|
+
f"{num_threads}-threaded execution took longer than 2-threaded baseline: {multi_duration:.2f}s vs {baseline_duration:.2f}s",
|
|
1347
|
+
UserWarning,
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1352
|
+
def test_gil_free_dots_packed_threading():
|
|
1353
|
+
"""Test multi-threaded dots_packed with start_row/end_row."""
|
|
1354
|
+
_skip_unless_free_threaded()
|
|
1355
|
+
|
|
1356
|
+
height, depth, width = 200, 64, 50
|
|
1357
|
+
num_threads = min(multiprocessing.cpu_count(), 4)
|
|
1358
|
+
left_matrix = np.random.randn(height, depth).astype(np.float32)
|
|
1359
|
+
right_matrix = np.ascontiguousarray(np.random.randn(width, depth).astype(np.float32))
|
|
1360
|
+
right_packed = nk.dots_pack(right_matrix, dtype="float32")
|
|
1361
|
+
|
|
1362
|
+
# Single-threaded reference
|
|
1363
|
+
reference = nk.dots_packed(left_matrix, right_packed)
|
|
1364
|
+
|
|
1365
|
+
# Multi-threaded with row slicing into shared output
|
|
1366
|
+
output = nk.zeros((height, width), dtype="float64")
|
|
1367
|
+
rows_per_thread = height // num_threads
|
|
1368
|
+
|
|
1369
|
+
def compute_slice(thread_index):
|
|
1370
|
+
start_row = thread_index * rows_per_thread
|
|
1371
|
+
end_row = start_row + rows_per_thread if thread_index < num_threads - 1 else height
|
|
1372
|
+
nk.dots_packed(left_matrix, right_packed, out=output, start_row=start_row, end_row=end_row)
|
|
1373
|
+
|
|
1374
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as pool:
|
|
1375
|
+
list(pool.map(compute_slice, range(num_threads)))
|
|
1376
|
+
|
|
1377
|
+
assert_allclose(
|
|
1378
|
+
np.array(output), np.array(reference), err_msg="Multi-threaded dots_packed result differs from single-threaded"
|
|
1379
|
+
)
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
@pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
|
|
1383
|
+
def test_gil_free_dots_symmetric_threading():
|
|
1384
|
+
"""Test multi-threaded dots_symmetric with start_row/end_row.
|
|
1385
|
+
|
|
1386
|
+
The symmetric kernel only fills the upper triangle of each row's output.
|
|
1387
|
+
Each thread computes a disjoint row range into the same shared output tensor.
|
|
1388
|
+
"""
|
|
1389
|
+
_skip_unless_free_threaded()
|
|
1390
|
+
|
|
1391
|
+
count, depth = 100, 64
|
|
1392
|
+
num_threads = min(multiprocessing.cpu_count(), 4)
|
|
1393
|
+
vectors = np.random.randn(count, depth).astype(np.float32)
|
|
1394
|
+
|
|
1395
|
+
# Single-threaded reference (upper triangle only is meaningful)
|
|
1396
|
+
reference = np.array(nk.dots_symmetric(vectors))
|
|
1397
|
+
|
|
1398
|
+
# Multi-threaded with row slicing into shared output
|
|
1399
|
+
output = nk.zeros((count, count), dtype="float64")
|
|
1400
|
+
rows_per_thread = count // num_threads
|
|
1401
|
+
|
|
1402
|
+
def compute_slice(thread_index):
|
|
1403
|
+
start_row = thread_index * rows_per_thread
|
|
1404
|
+
end_row = start_row + rows_per_thread if thread_index < num_threads - 1 else count
|
|
1405
|
+
nk.dots_symmetric(vectors, out=output, start_row=start_row, end_row=end_row)
|
|
1406
|
+
|
|
1407
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as pool:
|
|
1408
|
+
list(pool.map(compute_slice, range(num_threads)))
|
|
1409
|
+
|
|
1410
|
+
# Only the upper triangle is guaranteed initialized by the symmetric kernel
|
|
1411
|
+
result = np.array(output)
|
|
1412
|
+
mask = np.triu(np.ones((count, count), dtype=bool))
|
|
1413
|
+
assert_allclose(
|
|
1414
|
+
result[mask],
|
|
1415
|
+
reference[mask],
|
|
1416
|
+
err_msg="Multi-threaded dots_symmetric upper triangle differs from single-threaded",
|
|
1417
|
+
)
|