usearch 2.26.1 → 2.26.3
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 +1 -1
- package/include/usearch/index.hpp +5 -1
- package/numkong/CITATION.cff +13 -9
- package/numkong/CONTRIBUTING.md +29 -24
- package/numkong/Cargo.toml +1 -1
- package/numkong/README.md +21 -7
- package/numkong/binding.gyp +47 -31
- package/numkong/build.rs +15 -5
- package/numkong/c/dispatch.h +0 -1
- package/numkong/c/parallel.c +139 -0
- package/numkong/c/parallel.h +47 -0
- package/numkong/cmake/toolchain-wasi.cmake +39 -5
- package/numkong/include/README.md +24 -17
- package/numkong/include/numkong/attention/sapphireamx.h +6 -6
- package/numkong/include/numkong/attention/sme.h +2 -2
- package/numkong/include/numkong/capabilities.h +2 -2
- package/numkong/include/numkong/cast/README.md +9 -7
- package/numkong/include/numkong/cast/loongsonasx.h +48 -40
- package/numkong/include/numkong/cast/serial.h +89 -11
- package/numkong/include/numkong/cast.h +3 -3
- package/numkong/include/numkong/curved/README.md +10 -10
- package/numkong/include/numkong/curved/neon.h +1 -0
- package/numkong/include/numkong/curved/neonbfdot.h +1 -0
- package/numkong/include/numkong/curved/smef64.h +1 -0
- package/numkong/include/numkong/dot/README.md +18 -18
- package/numkong/include/numkong/dot/alder.h +8 -8
- package/numkong/include/numkong/dot/haswell.h +8 -8
- package/numkong/include/numkong/dot/icelake.h +12 -12
- package/numkong/include/numkong/dot/loongsonasx.h +6 -6
- package/numkong/include/numkong/dot/neon.h +2 -1
- package/numkong/include/numkong/dot/neonbfdot.h +1 -0
- package/numkong/include/numkong/dot/neonfhm.h +1 -0
- package/numkong/include/numkong/dot/neonsdot.h +1 -0
- package/numkong/include/numkong/dot/powervsx.h +3 -3
- package/numkong/include/numkong/dot/serial.h +19 -6
- package/numkong/include/numkong/dot/skylake.h +4 -4
- package/numkong/include/numkong/dot/svehalf.h +1 -1
- package/numkong/include/numkong/dot/v128relaxed.h +24 -23
- package/numkong/include/numkong/dots/README.md +26 -26
- package/numkong/include/numkong/dots/genoa.h +3 -1
- package/numkong/include/numkong/dots/icelake.h +1 -0
- package/numkong/include/numkong/dots/powervsx.h +1 -1
- package/numkong/include/numkong/dots/serial.h +32 -33
- package/numkong/include/numkong/dots/sierra.h +4 -3
- package/numkong/include/numkong/each/README.md +5 -3
- package/numkong/include/numkong/each/sapphire.h +2 -0
- package/numkong/include/numkong/each/skylake.h +1 -0
- package/numkong/include/numkong/geospatial/README.md +6 -6
- package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
- package/numkong/include/numkong/maxsim/README.md +5 -5
- package/numkong/include/numkong/maxsim/genoa.h +4 -0
- package/numkong/include/numkong/maxsim/icelake.h +1 -0
- package/numkong/include/numkong/maxsim/serial.h +22 -9
- package/numkong/include/numkong/maxsim/sme.h +3 -2
- package/numkong/include/numkong/mesh/README.md +7 -4
- package/numkong/include/numkong/mesh/genoa.h +2 -2
- package/numkong/include/numkong/mesh/haswell.h +8 -8
- package/numkong/include/numkong/mesh/neon.h +6 -6
- package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
- package/numkong/include/numkong/mesh/neonfhm.h +3 -2
- package/numkong/include/numkong/mesh/rvv.h +4 -4
- package/numkong/include/numkong/mesh/serial.h +17 -17
- package/numkong/include/numkong/mesh/skylake.h +4 -4
- package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
- package/numkong/include/numkong/mesh.hpp +2 -2
- package/numkong/include/numkong/numkong.h +1 -1
- package/numkong/include/numkong/numkong.hpp +1 -1
- package/numkong/include/numkong/probability/README.md +17 -15
- package/numkong/include/numkong/probability/skylake.h +1 -1
- package/numkong/include/numkong/reduce/README.md +27 -21
- package/numkong/include/numkong/reduce/genoa.h +2 -1
- package/numkong/include/numkong/reduce/icelake.h +1 -0
- package/numkong/include/numkong/reduce/neon.h +1 -1
- package/numkong/include/numkong/reduce/serial.h +9 -9
- package/numkong/include/numkong/reduce/skylake.h +4 -4
- package/numkong/include/numkong/reduce.hpp +38 -0
- package/numkong/include/numkong/scalar/README.md +29 -28
- package/numkong/include/numkong/set/README.md +17 -14
- package/numkong/include/numkong/set/haswell.h +15 -16
- package/numkong/include/numkong/set/icelake.h +15 -16
- package/numkong/include/numkong/set/loongsonasx.h +12 -10
- package/numkong/include/numkong/set/neon.h +15 -19
- package/numkong/include/numkong/set/powervsx.h +15 -21
- package/numkong/include/numkong/set/serial.h +19 -20
- package/numkong/include/numkong/set/v128relaxed.h +10 -8
- package/numkong/include/numkong/sets/icelake.h +1 -0
- package/numkong/include/numkong/sparse/README.md +4 -4
- package/numkong/include/numkong/sparse/icelake.h +1 -0
- package/numkong/include/numkong/sparse/neon.h +1 -0
- package/numkong/include/numkong/sparse/serial.h +10 -3
- package/numkong/include/numkong/sparse/turin.h +1 -0
- package/numkong/include/numkong/spatial/README.md +25 -24
- package/numkong/include/numkong/spatial/alder.h +2 -1
- package/numkong/include/numkong/spatial/haswell.h +48 -40
- package/numkong/include/numkong/spatial/icelake.h +2 -1
- package/numkong/include/numkong/spatial/neon.h +54 -46
- package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
- package/numkong/include/numkong/spatial/powervsx.h +54 -46
- package/numkong/include/numkong/spatial/serial.h +56 -46
- package/numkong/include/numkong/spatial/sierra.h +4 -3
- package/numkong/include/numkong/spatial/skylake.h +27 -22
- package/numkong/include/numkong/spatial/sve.h +6 -6
- package/numkong/include/numkong/spatial/svebfdot.h +1 -1
- package/numkong/include/numkong/spatial/svehalf.h +1 -1
- package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
- package/numkong/include/numkong/spatials/README.md +15 -15
- package/numkong/include/numkong/spatials/genoa.h +3 -0
- package/numkong/include/numkong/spatials/graniteamx.h +2 -0
- package/numkong/include/numkong/spatials/icelake.h +1 -0
- package/numkong/include/numkong/spatials/sierra.h +1 -0
- package/numkong/include/numkong/tensor.hpp +236 -123
- package/numkong/include/numkong/trigonometry/README.md +4 -4
- package/numkong/include/numkong/trigonometry/serial.h +6 -6
- package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
- package/numkong/include/numkong/types.hpp +46 -16
- package/numkong/include/numkong/vector.hpp +116 -23
- package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
- package/numkong/javascript/README.md +18 -16
- package/numkong/javascript/numkong.c +70 -40
- package/numkong/package.json +8 -8
- package/numkong/rust/README.md +19 -9
- package/numkong/rust/cast.rs +52 -1
- package/numkong/rust/each.rs +480 -7
- package/numkong/rust/matrix.rs +877 -216
- package/numkong/rust/numkong.rs +12 -8
- package/numkong/rust/reduce.rs +433 -5
- package/numkong/rust/tensor.rs +450 -842
- package/numkong/rust/vector.rs +520 -101
- package/numkong/swift/README.md +5 -3
- package/numkong/test/README.md +4 -4
- package/numkong/test/test-wasi.mjs +17 -0
- package/numkong/test/test_each.cpp +30 -1
- package/numkong/test/test_each.py +74 -1
- package/numkong/test/test_reduce.cpp +51 -0
- package/numkong/test/test_tensor.cpp +2 -63
- package/numkong/test/test_tensor.py +93 -0
- package/package.json +1 -1
- 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/prebuilds/win32-x64/usearch.node +0 -0
|
@@ -953,7 +953,7 @@ NK_PUBLIC void nk_reduce_moments_f64_skylake( //
|
|
|
953
953
|
NK_INTERNAL void nk_reduce_moments_i8_skylake_contiguous_( //
|
|
954
954
|
nk_i8_t const *data_ptr, nk_size_t count, //
|
|
955
955
|
nk_i64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
|
|
956
|
-
// Sum: VPSADBW with XOR bias
|
|
956
|
+
// Sum: VPSADBW with XOR bias.
|
|
957
957
|
// Sumsq: widen i8→i16, VPMADDWD(x,x) → i32 (pairs of squares), accumulate i32.
|
|
958
958
|
// i32 overflow safe: max per lane = (128² + 128²) * 65536 iters ≈ 2.1B = safe limit.
|
|
959
959
|
// The dispatch recurses at (NK_U16_MAX+1)*64 elements → at most 65536 iterations here.
|
|
@@ -1147,7 +1147,7 @@ NK_PUBLIC void nk_reduce_minmax_i8_skylake( //
|
|
|
1147
1147
|
NK_INTERNAL void nk_reduce_moments_u8_skylake_contiguous_( //
|
|
1148
1148
|
nk_u8_t const *data_ptr, nk_size_t count, //
|
|
1149
1149
|
nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
|
|
1150
|
-
// Sum: VPSADBW directly
|
|
1150
|
+
// Sum: VPSADBW directly.
|
|
1151
1151
|
// Sumsq: widen u8→i16, VPMADDWD(x,x) → i32 (pairs of squares), accumulate i32.
|
|
1152
1152
|
// i32 overflow safe: max per lane = (255² + 255²) * 1024 iters ≈ 133M < 2.1B.
|
|
1153
1153
|
__m512i zero_u8x64 = _mm512_setzero_si512();
|
|
@@ -1697,7 +1697,7 @@ NK_PUBLIC void nk_reduce_minmax_u16_skylake( //
|
|
|
1697
1697
|
max_index_ptr);
|
|
1698
1698
|
}
|
|
1699
1699
|
|
|
1700
|
-
/** @brief Unsigned saturating add of two
|
|
1700
|
+
/** @brief Unsigned saturating add of two u64x8 vectors (3 uops). */
|
|
1701
1701
|
NK_INTERNAL __m512i nk_u64_sadd_epi64_skylake_(__m512i a, __m512i b) {
|
|
1702
1702
|
__m512i result_u64x8 = _mm512_add_epi64(a, b);
|
|
1703
1703
|
__mmask8 ovf = _mm512_cmp_epu64_mask(result_u64x8, a, _MM_CMPINT_LT);
|
|
@@ -3371,7 +3371,7 @@ NK_PUBLIC void nk_reduce_moments_u4_skylake( //
|
|
|
3371
3371
|
NK_INTERNAL void nk_reduce_moments_u1_skylake_contiguous_( //
|
|
3372
3372
|
nk_u1x8_t const *data_ptr, nk_size_t count, //
|
|
3373
3373
|
nk_u64_t *sum_ptr, nk_u64_t *sumsq_ptr) {
|
|
3374
|
-
// Sum = popcount via 4-bit LUT
|
|
3374
|
+
// Sum = popcount via 4-bit LUT. Sumsq = sum for bits.
|
|
3375
3375
|
__m512i lut_i8x64 = _mm512_set_epi8( //
|
|
3376
3376
|
4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0, //
|
|
3377
3377
|
4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0, //
|
|
@@ -629,6 +629,44 @@ std::size_t argmax(vector_view<value_type_> input) noexcept {
|
|
|
629
629
|
return minmax(input).max_index;
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
+
/** @brief Population count of a packed bit tensor — number of set bits.
|
|
633
|
+
* Wraps `nk_reduce_moments_u1` via `sum`; returns 0 for empty inputs. */
|
|
634
|
+
template <std::size_t max_rank_ = 8>
|
|
635
|
+
u64_t popcount(tensor_view<u1x8_t, max_rank_> input) noexcept {
|
|
636
|
+
return sum<u1x8_t, max_rank_>(input);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** @brief True if any bit of the packed bit tensor is set. Suffixed `_set` to
|
|
640
|
+
* avoid colliding with the `all_t`/`all` slice marker in `vector.hpp`. */
|
|
641
|
+
template <std::size_t max_rank_ = 8>
|
|
642
|
+
bool any_set(tensor_view<u1x8_t, max_rank_> input) noexcept {
|
|
643
|
+
return popcount<max_rank_>(input).raw_ != 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** @brief True if no bit of the packed bit tensor is set. */
|
|
647
|
+
template <std::size_t max_rank_ = 8>
|
|
648
|
+
bool none_set(tensor_view<u1x8_t, max_rank_> input) noexcept {
|
|
649
|
+
return !any_set<max_rank_>(input);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** @brief True if every bit of the packed bit tensor is set. */
|
|
653
|
+
template <std::size_t max_rank_ = 8>
|
|
654
|
+
bool all_set(tensor_view<u1x8_t, max_rank_> input) noexcept {
|
|
655
|
+
return popcount<max_rank_>(input).raw_ == input.numel();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** @brief Population count over a 1D bit-vector view. */
|
|
659
|
+
inline u64_t popcount(vector_view<u1x8_t> input) noexcept { return sum<u1x8_t>(input); }
|
|
660
|
+
|
|
661
|
+
/** @brief True if any bit of the 1D bit-vector view is set. */
|
|
662
|
+
inline bool any_set(vector_view<u1x8_t> input) noexcept { return popcount(input).raw_ != 0; }
|
|
663
|
+
|
|
664
|
+
/** @brief True if no bit of the 1D bit-vector view is set. */
|
|
665
|
+
inline bool none_set(vector_view<u1x8_t> input) noexcept { return !any_set(input); }
|
|
666
|
+
|
|
667
|
+
/** @brief True if every bit of the 1D bit-vector view is set. */
|
|
668
|
+
inline bool all_set(vector_view<u1x8_t> input) noexcept { return popcount(input).raw_ == input.size(); }
|
|
669
|
+
|
|
632
670
|
#pragma endregion Scalar Reductions
|
|
633
671
|
|
|
634
672
|
#pragma region Axis Reductions
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
NumKong provides single-element math operations — square root, reciprocal square root, fused multiply-add, and saturating integer arithmetic — with per-ISA implementations.
|
|
4
4
|
These primitives serve as building blocks for vectorized kernels: distance finalizers call `nk_f32_rsqrt` for angular normalization, packing routines call `nk_f32_sqrt` for norm computation.
|
|
5
|
-
Ordering functions (`nk_f16_order`, `nk_bf16_order`, `nk_e4m3_order`)
|
|
5
|
+
Ordering functions (`nk_f16_order`, `nk_bf16_order`, `nk_e4m3_order`) compare two values through their bit patterns, returning a negative, zero, or positive `int` like `strcmp`.
|
|
6
6
|
|
|
7
7
|
Reciprocal square root:
|
|
8
8
|
|
|
@@ -40,27 +40,27 @@ def saturating_add(a: int, b: int, bits: int, signed: bool) -> int:
|
|
|
40
40
|
|
|
41
41
|
## Input & Output Types
|
|
42
42
|
|
|
43
|
-
| Input Type | Output Type | Description
|
|
44
|
-
| ---------- | ----------- |
|
|
45
|
-
| `f64` | `f64` | sqrt, rsqrt, fma for 64-bit doubles
|
|
46
|
-
| `f32` | `f32` | sqrt, rsqrt, fma for 32-bit floats
|
|
47
|
-
| `f16` | `f16` | sqrt, rsqrt, fma for 16-bit halfs
|
|
48
|
-
| `i8` | `i8` | Saturating add and multiply
|
|
49
|
-
| `u8` | `u8` | Saturating add and multiply
|
|
50
|
-
| `i16` | `i16` | Saturating add and multiply
|
|
51
|
-
| `u16` | `u16` | Saturating add and multiply
|
|
52
|
-
| `i32` | `i32` | Saturating add and multiply
|
|
53
|
-
| `u32` | `u32` | Saturating add and multiply
|
|
54
|
-
| `i64` | `i64` | Saturating add and multiply
|
|
55
|
-
| `u64` | `u64` | Saturating add and multiply
|
|
56
|
-
| `i4x2` | `i4x2` | Saturating add for packed signed nibble pairs |
|
|
57
|
-
| `u4x2` | `u4x2` | Saturating add for packed unsigned nibble pairs |
|
|
58
|
-
| `f16` | `
|
|
59
|
-
| `bf16` | `
|
|
60
|
-
| `e4m3` | `
|
|
61
|
-
| `e5m2` | `
|
|
62
|
-
| `e2m3` | `
|
|
63
|
-
| `e3m2` | `
|
|
43
|
+
| Input Type | Output Type | Description |
|
|
44
|
+
| ---------- | ----------- | ------------------------------------------------------------ |
|
|
45
|
+
| `f64` | `f64` | sqrt, rsqrt, fma for 64-bit doubles |
|
|
46
|
+
| `f32` | `f32` | sqrt, rsqrt, fma for 32-bit floats |
|
|
47
|
+
| `f16` | `f16` | sqrt, rsqrt, fma for 16-bit halfs |
|
|
48
|
+
| `i8` | `i8` | Saturating add and multiply |
|
|
49
|
+
| `u8` | `u8` | Saturating add and multiply |
|
|
50
|
+
| `i16` | `i16` | Saturating add and multiply |
|
|
51
|
+
| `u16` | `u16` | Saturating add and multiply |
|
|
52
|
+
| `i32` | `i32` | Saturating add and multiply |
|
|
53
|
+
| `u32` | `u32` | Saturating add and multiply |
|
|
54
|
+
| `i64` | `i64` | Saturating add and multiply |
|
|
55
|
+
| `u64` | `u64` | Saturating add and multiply |
|
|
56
|
+
| `i4x2` | `i4x2` | Saturating add and multiply for packed signed nibble pairs |
|
|
57
|
+
| `u4x2` | `u4x2` | Saturating add and multiply for packed unsigned nibble pairs |
|
|
58
|
+
| `f16` | `int` | Ordering: three-way comparison of two values |
|
|
59
|
+
| `bf16` | `int` | Ordering: three-way comparison of two values |
|
|
60
|
+
| `e4m3` | `int` | Ordering: three-way comparison of two values |
|
|
61
|
+
| `e5m2` | `int` | Ordering: three-way comparison of two values |
|
|
62
|
+
| `e2m3` | `int` | Ordering: three-way comparison of two values |
|
|
63
|
+
| `e3m2` | `int` | Ordering: three-way comparison of two values |
|
|
64
64
|
|
|
65
65
|
## Optimizations
|
|
66
66
|
|
|
@@ -68,20 +68,21 @@ def saturating_add(a: int, b: int, bits: int, signed: bool) -> int:
|
|
|
68
68
|
|
|
69
69
|
`nk_f32_rsqrt_serial` uses the classic bit-manipulation trick: reinterpret Float32 bits as Int32, compute `0x5F375A86 - (bits >> 1)`, reinterpret back to Float32, then refine with 3 Newton-Raphson iterations reaching ~34.9 correct bits.
|
|
70
70
|
Each Newton-Raphson iteration: `y = y * (1.5f - 0.5f * x * y * y)` — 2 multiplies and 1 subtract, ~4cy per iteration.
|
|
71
|
-
`nk_f32_rsqrt_haswell` replaces this with hardware `
|
|
71
|
+
`nk_f32_rsqrt_haswell` replaces this with hardware `RSQRTSS` ($2^{-14}$ relative error, ~4cy latency) plus one Newton-Raphson refinement (~22-24 correct bits).
|
|
72
72
|
`nk_f64_rsqrt_serial` uses the Float64 magic constant `0x5FE6EB50C7B537A9` with 4 iterations for 52-bit mantissa coverage.
|
|
73
73
|
|
|
74
74
|
### Dekker Error-Free Multiplication for FMA
|
|
75
75
|
|
|
76
|
-
`nk_f32_fma_serial` emulates fused multiply-add on platforms without hardware FMA using Dekker's algorithm: splits each operand into high and low halves via `
|
|
77
|
-
The magic constant $
|
|
76
|
+
`nk_f32_fma_serial` emulates fused multiply-add on platforms without hardware FMA using Dekker's algorithm: splits each operand into high and low halves via `a_high = (a * 4097.0f) - ((a * 4097.0f) - a)`, then computes the exact product error term.
|
|
77
|
+
The magic constant $4097 = 2^{12} + 1$ splits a 24-bit mantissa into two 12-bit halves that multiply without rounding; `nk_f64_fma_serial` uses $134217729 = 2^{27} + 1$ for the 53-bit mantissa.
|
|
78
78
|
`nk_f32_fma_haswell` uses hardware `VFMADD231SS` — single instruction, single cycle, exact to the last bit.
|
|
79
79
|
|
|
80
80
|
### Float-to-Integer Ordering
|
|
81
81
|
|
|
82
|
-
`nk_f16_order_serial`, `nk_bf16_order_serial`, `nk_e4m3_order_serial`
|
|
83
|
-
Positive floats are already ordered by their bit patterns; negative floats need bit inversion
|
|
84
|
-
This
|
|
82
|
+
`nk_f16_order_serial`, `nk_bf16_order_serial`, `nk_e4m3_order_serial` map floating-point bit patterns onto signed integers that preserve the total order, then return the difference of the two keys.
|
|
83
|
+
Positive floats are already ordered by their bit patterns; negative floats need bit inversion, applied branchlessly as `bits ^ -sign`.
|
|
84
|
+
This keeps floating-point ordering on the integer pipeline — used by `nk_reduce_minmax_*` for Float8 and sub-32-bit types that lack native SIMD comparison.
|
|
85
|
+
`nk_f16_order_sapphire` instead uses the native `VCOMISH` half-precision comparisons.
|
|
85
86
|
|
|
86
87
|
## Performance
|
|
87
88
|
|
|
@@ -53,36 +53,39 @@ def jaccard_words(a: np.ndarray, b: np.ndarray) -> float:
|
|
|
53
53
|
|
|
54
54
|
## Optimizations
|
|
55
55
|
|
|
56
|
-
### Harley-Seal Carry-Save Adders
|
|
56
|
+
### Popcount Without Harley-Seal Carry-Save Adders
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
Instead of computing popcount on every XOR/AND/OR result independently, three intermediate values are combined through a full-adder circuit:
|
|
58
|
+
The textbook way to amortize population counts is a Harley-Seal carry-save adder tree, which folds three inputs through a full-adder circuit before any popcount is issued:
|
|
60
59
|
|
|
61
60
|
```
|
|
62
61
|
ones = a ^ b ^ c
|
|
63
62
|
twos = (a & b) | (c & (a ^ b))
|
|
64
63
|
```
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
The
|
|
65
|
+
Chaining levels yields `fours` and `eights` accumulators, each weighted by a power of two, so popcount runs on the accumulators instead of on every input vector — roughly a third of the calls.
|
|
66
|
+
NumKong does not do this.
|
|
67
|
+
The cycles-per-byte measurements in `set.h` put the crossover above 1 KB per vector: below it, loop and reduction overhead outweighs the saved popcounts, and the largest binary input benchmarked here is 4096 bits, or 512 bytes.
|
|
68
|
+
Ice Lake and later expose `VPOPCNTQ` outright, and on Genoa it dual-issues on ports 0-1, so the popcount pressure that CSA trees relieve is largely absent on the hardware that could run them.
|
|
69
|
+
|
|
70
|
+
AVX2 has no vector population count at all, so `nk_hamming_u1_haswell` and `nk_jaccard_u1_haswell` walk the packed octets 8 bytes at a time and call the scalar `POPCNT` through `_mm_popcnt_u64`.
|
|
71
|
+
`nk_hamming_u1_neon` and `nk_jaccard_u1_neon` use the NEON `CNT` instruction instead, accumulating per-byte counts in a `uint8x16_t` for at most 31 iterations before widening, since 31 x 8 still fits in a byte.
|
|
69
72
|
|
|
70
73
|
### Native VPOPCNTQ on Ice Lake
|
|
71
74
|
|
|
72
75
|
`nk_hamming_u1_icelake`, `nk_jaccard_u1_icelake` use `VPOPCNTQ` on 512-bit vectors, which directly produces per-quadword population counts for 8 quadwords at once.
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
Inputs of up to 256 bytes are fully unrolled into one, two, three, or four masked register pairs with no loop at all, since binary metrics are hard to speed up at tiny lengths.
|
|
77
|
+
Longer inputs enter a loop that accumulates the per-quadword counts via `VPADDQ` and reduces once at the end.
|
|
75
78
|
|
|
76
79
|
### Jaccard via Precomputed Norms
|
|
77
80
|
|
|
78
|
-
`
|
|
79
|
-
|
|
81
|
+
The streaming helpers `nk_jaccard_u1x64_finalize_haswell` and `nk_jaccard_u1x512_finalize_icelake` exploit the identity $|A \cup B| = |A| + |B| - |A \cap B|$ to avoid computing both AND-popcount and OR-popcount in the inner loop.
|
|
82
|
+
The matching `update` step accumulates only the intersection popcount; `finalize` takes the query popcount and a vector of four target popcounts and recovers the union from them, halving the work in the critical path.
|
|
83
|
+
Both finalizers replace the division by a reciprocal estimate — `RCPPS` on Haswell, `VRCP14PS` on Ice Lake — refined with one Newton-Raphson step.
|
|
80
84
|
|
|
81
|
-
### Byte Hamming via
|
|
85
|
+
### Byte Hamming via Compare Masks
|
|
82
86
|
|
|
83
|
-
`nk_hamming_u8_haswell
|
|
84
|
-
|
|
85
|
-
Since XOR results are either 0 or nonzero (not necessarily 1), the kernel masks XOR output through `VPMIN` with a vector of ones to clamp each byte to 0 or 1 before feeding `VPSADBW`.
|
|
87
|
+
`nk_hamming_u8_haswell` compares 32 bytes at a time with `VPCMPEQB`, extracts the per-byte sign bits of the two 128-bit halves with `VPMOVMSKB`, inverts the masks, and counts the set bits with scalar `POPCNT`.
|
|
88
|
+
`nk_hamming_u8_icelake` does the same over 64 bytes with `VPCMPNEQB`, which writes the mismatch mask straight into a `__mmask64`, so no extract step is needed.
|
|
86
89
|
|
|
87
90
|
## Performance
|
|
88
91
|
|
|
@@ -31,8 +31,7 @@
|
|
|
31
31
|
* nk_jaccard_u1x64_init_haswell(&state_first);
|
|
32
32
|
* // ... stream through packed binary vectors ...
|
|
33
33
|
* nk_jaccard_u1x64_finalize_haswell(&state_first, &state_second, &state_third, &state_fourth,
|
|
34
|
-
* query_popcount,
|
|
35
|
-
* total_dimensions, &results);
|
|
34
|
+
* query_popcount, &target_popcounts_vec, total_dimensions, &result_vec);
|
|
36
35
|
* @endcode
|
|
37
36
|
*/
|
|
38
37
|
#ifndef NK_SET_HASWELL_H
|
|
@@ -240,8 +239,8 @@ NK_INTERNAL void nk_jaccard_u1x64_update_haswell(nk_jaccard_u1x64_state_haswell_
|
|
|
240
239
|
NK_INTERNAL void nk_jaccard_u1x64_finalize_haswell( //
|
|
241
240
|
nk_jaccard_u1x64_state_haswell_t const *state_a, nk_jaccard_u1x64_state_haswell_t const *state_b,
|
|
242
241
|
nk_jaccard_u1x64_state_haswell_t const *state_c, nk_jaccard_u1x64_state_haswell_t const *state_d,
|
|
243
|
-
nk_f32_t query_popcount,
|
|
244
|
-
|
|
242
|
+
nk_f32_t query_popcount, nk_b128_vec_t const *target_popcounts_vec, nk_size_t total_dimensions,
|
|
243
|
+
nk_b128_vec_t *result_vec) {
|
|
245
244
|
nk_unused_(total_dimensions);
|
|
246
245
|
|
|
247
246
|
// 4-way SIMD Jaccard computation with fast reciprocal.
|
|
@@ -263,7 +262,7 @@ NK_INTERNAL void nk_jaccard_u1x64_finalize_haswell( //
|
|
|
263
262
|
__m128 intersection_f32x4 = _mm_setr_ps(intersection_a_f32, intersection_b_f32, intersection_c_f32,
|
|
264
263
|
intersection_d_f32);
|
|
265
264
|
__m128 query_f32x4 = _mm_set1_ps(query_popcount);
|
|
266
|
-
__m128 targets_f32x4 =
|
|
265
|
+
__m128 targets_f32x4 = target_popcounts_vec->xmm_ps;
|
|
267
266
|
__m128 union_f32x4 = _mm_sub_ps(_mm_add_ps(query_f32x4, targets_f32x4), intersection_f32x4);
|
|
268
267
|
|
|
269
268
|
// Handle zero-union edge case
|
|
@@ -283,24 +282,24 @@ NK_INTERNAL void nk_jaccard_u1x64_finalize_haswell( //
|
|
|
283
282
|
|
|
284
283
|
__m128 ratio_f32x4 = _mm_mul_ps(intersection_f32x4, union_reciprocal_f32x4);
|
|
285
284
|
__m128 jaccard_f32x4 = _mm_sub_ps(one_f32x4, ratio_f32x4);
|
|
286
|
-
|
|
285
|
+
result_vec->xmm_ps = _mm_blendv_ps(jaccard_f32x4, _mm_setzero_ps(), zero_union_mask);
|
|
287
286
|
}
|
|
288
287
|
|
|
289
288
|
/** @brief Hamming from_dot: computes pop_a + pop_b - 2*dot for 4 pairs (Haswell). */
|
|
290
|
-
NK_INTERNAL void nk_hamming_u32x4_from_dot_haswell_(nk_b128_vec_t
|
|
291
|
-
nk_b128_vec_t *
|
|
292
|
-
__m128i dots_i32x4 =
|
|
289
|
+
NK_INTERNAL void nk_hamming_u32x4_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
290
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
291
|
+
__m128i dots_i32x4 = dots_vec->xmm;
|
|
293
292
|
__m128i query_i32x4 = _mm_set1_epi32((int)query_pop);
|
|
294
|
-
__m128i target_i32x4 =
|
|
295
|
-
|
|
293
|
+
__m128i target_i32x4 = target_pops_vec->xmm;
|
|
294
|
+
result_vec->xmm = _mm_sub_epi32(_mm_add_epi32(query_i32x4, target_i32x4), _mm_slli_epi32(dots_i32x4, 1));
|
|
296
295
|
}
|
|
297
296
|
|
|
298
297
|
/** @brief Jaccard from_dot: computes 1 - dot / (pop_a + pop_b - dot) for 4 pairs (Haswell). */
|
|
299
|
-
NK_INTERNAL void nk_jaccard_f32x4_from_dot_haswell_(nk_b128_vec_t
|
|
300
|
-
nk_b128_vec_t *
|
|
301
|
-
__m128 dot_f32x4 = _mm_cvtepi32_ps(
|
|
298
|
+
NK_INTERNAL void nk_jaccard_f32x4_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
299
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
300
|
+
__m128 dot_f32x4 = _mm_cvtepi32_ps(dots_vec->xmm);
|
|
302
301
|
__m128 query_f32x4 = _mm_set1_ps((nk_f32_t)query_pop);
|
|
303
|
-
__m128 target_f32x4 = _mm_cvtepi32_ps(
|
|
302
|
+
__m128 target_f32x4 = _mm_cvtepi32_ps(target_pops_vec->xmm);
|
|
304
303
|
__m128 union_f32x4 = _mm_sub_ps(_mm_add_ps(query_f32x4, target_f32x4), dot_f32x4);
|
|
305
304
|
|
|
306
305
|
__m128 zero_union_mask = _mm_cmpeq_ps(union_f32x4, _mm_setzero_ps());
|
|
@@ -314,7 +313,7 @@ NK_INTERNAL void nk_jaccard_f32x4_from_dot_haswell_(nk_b128_vec_t dots, nk_u32_t
|
|
|
314
313
|
|
|
315
314
|
__m128 ratio_f32x4 = _mm_mul_ps(dot_f32x4, union_reciprocal_f32x4);
|
|
316
315
|
__m128 jaccard_f32x4 = _mm_sub_ps(one_f32x4, ratio_f32x4);
|
|
317
|
-
|
|
316
|
+
result_vec->xmm_ps = _mm_blendv_ps(jaccard_f32x4, _mm_setzero_ps(), zero_union_mask);
|
|
318
317
|
}
|
|
319
318
|
|
|
320
319
|
#pragma endregion Stateful Streaming
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
* nk_jaccard_u1x512_init_icelake(&state_first);
|
|
31
31
|
* // ... stream through packed binary vectors ...
|
|
32
32
|
* nk_jaccard_u1x512_finalize_icelake(&state_first, &state_second, &state_third, &state_fourth,
|
|
33
|
-
* query_popcount,
|
|
34
|
-
* total_dimensions, &results);
|
|
33
|
+
* query_popcount, &target_popcounts_vec, total_dimensions, &result_vec);
|
|
35
34
|
* @endcode
|
|
36
35
|
*/
|
|
37
36
|
#ifndef NK_SET_ICELAKE_H
|
|
@@ -370,8 +369,8 @@ NK_INTERNAL void nk_jaccard_u1x512_update_icelake(nk_jaccard_u1x512_state_icelak
|
|
|
370
369
|
NK_INTERNAL void nk_jaccard_u1x512_finalize_icelake( //
|
|
371
370
|
nk_jaccard_u1x512_state_icelake_t const *state_a, nk_jaccard_u1x512_state_icelake_t const *state_b,
|
|
372
371
|
nk_jaccard_u1x512_state_icelake_t const *state_c, nk_jaccard_u1x512_state_icelake_t const *state_d,
|
|
373
|
-
nk_f32_t query_popcount,
|
|
374
|
-
|
|
372
|
+
nk_f32_t query_popcount, nk_b128_vec_t const *target_popcounts_vec, nk_size_t total_dimensions,
|
|
373
|
+
nk_b128_vec_t *result_vec) {
|
|
375
374
|
nk_unused_(total_dimensions);
|
|
376
375
|
|
|
377
376
|
// Port-optimized 4-way horizontal reduction using early i64 → i32 truncation.
|
|
@@ -415,7 +414,7 @@ NK_INTERNAL void nk_jaccard_u1x512_finalize_icelake( //
|
|
|
415
414
|
// Compute Jaccard distance: 1 - intersection ÷ union
|
|
416
415
|
// where union = query_popcount + target_popcount - intersection
|
|
417
416
|
__m128 query_f32x4 = _mm_set1_ps(query_popcount);
|
|
418
|
-
__m128 targets_f32x4 =
|
|
417
|
+
__m128 targets_f32x4 = target_popcounts_vec->xmm_ps;
|
|
419
418
|
__m128 union_f32x4 = _mm_sub_ps(_mm_add_ps(query_f32x4, targets_f32x4), intersection_f32x4);
|
|
420
419
|
|
|
421
420
|
// Handle zero-union edge case: if union == 0, result = 0.0
|
|
@@ -435,24 +434,24 @@ NK_INTERNAL void nk_jaccard_u1x512_finalize_icelake( //
|
|
|
435
434
|
|
|
436
435
|
__m128 ratio_f32x4 = _mm_mul_ps(intersection_f32x4, union_reciprocal_f32x4);
|
|
437
436
|
__m128 jaccard_f32x4 = _mm_sub_ps(one_f32x4, ratio_f32x4);
|
|
438
|
-
|
|
437
|
+
result_vec->xmm_ps = _mm_blendv_ps(jaccard_f32x4, _mm_setzero_ps(), zero_union_mask);
|
|
439
438
|
}
|
|
440
439
|
|
|
441
440
|
/** @brief Hamming from_dot: computes pop_a + pop_b - 2*dot for 4 pairs (Icelake). */
|
|
442
|
-
NK_INTERNAL void nk_hamming_u32x4_from_dot_icelake_(nk_b128_vec_t
|
|
443
|
-
nk_b128_vec_t *
|
|
444
|
-
__m128i dots_i32x4 =
|
|
441
|
+
NK_INTERNAL void nk_hamming_u32x4_from_dot_icelake_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
442
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
443
|
+
__m128i dots_i32x4 = dots_vec->xmm;
|
|
445
444
|
__m128i query_i32x4 = _mm_set1_epi32((int)query_pop);
|
|
446
|
-
__m128i target_i32x4 =
|
|
447
|
-
|
|
445
|
+
__m128i target_i32x4 = target_pops_vec->xmm;
|
|
446
|
+
result_vec->xmm = _mm_sub_epi32(_mm_add_epi32(query_i32x4, target_i32x4), _mm_slli_epi32(dots_i32x4, 1));
|
|
448
447
|
}
|
|
449
448
|
|
|
450
449
|
/** @brief Jaccard from_dot: computes 1 - dot / (pop_a + pop_b - dot) for 4 pairs (Icelake). */
|
|
451
|
-
NK_INTERNAL void nk_jaccard_f32x4_from_dot_icelake_(nk_b128_vec_t
|
|
452
|
-
nk_b128_vec_t *
|
|
453
|
-
__m128 dot_f32x4 = _mm_cvtepi32_ps(
|
|
450
|
+
NK_INTERNAL void nk_jaccard_f32x4_from_dot_icelake_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
451
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
452
|
+
__m128 dot_f32x4 = _mm_cvtepi32_ps(dots_vec->xmm);
|
|
454
453
|
__m128 query_f32x4 = _mm_set1_ps((nk_f32_t)query_pop);
|
|
455
|
-
__m128 target_f32x4 = _mm_cvtepi32_ps(
|
|
454
|
+
__m128 target_f32x4 = _mm_cvtepi32_ps(target_pops_vec->xmm);
|
|
456
455
|
__m128 union_f32x4 = _mm_sub_ps(_mm_add_ps(query_f32x4, target_f32x4), dot_f32x4);
|
|
457
456
|
|
|
458
457
|
__m128 zero_union_mask = _mm_cmpeq_ps(union_f32x4, _mm_setzero_ps());
|
|
@@ -465,7 +464,7 @@ NK_INTERNAL void nk_jaccard_f32x4_from_dot_icelake_(nk_b128_vec_t dots, nk_u32_t
|
|
|
465
464
|
|
|
466
465
|
__m128 ratio_f32x4 = _mm_mul_ps(dot_f32x4, union_reciprocal_f32x4);
|
|
467
466
|
__m128 jaccard_f32x4 = _mm_sub_ps(one_f32x4, ratio_f32x4);
|
|
468
|
-
|
|
467
|
+
result_vec->xmm_ps = _mm_blendv_ps(jaccard_f32x4, _mm_setzero_ps(), zero_union_mask);
|
|
469
468
|
}
|
|
470
469
|
|
|
471
470
|
#pragma endregion Stateful Streaming
|
|
@@ -144,20 +144,22 @@ NK_PUBLIC void nk_hamming_u8_loongsonasx(nk_u8_t const *a, nk_u8_t const *b, nk_
|
|
|
144
144
|
#pragma region Batched Finalizers
|
|
145
145
|
|
|
146
146
|
/** @brief Hamming from_dot: computes pop_a + pop_b − 2 × dot for 4 pairs (LSX). */
|
|
147
|
-
NK_INTERNAL void nk_hamming_u32x4_from_dot_loongsonasx_(nk_b128_vec_t
|
|
148
|
-
nk_b128_vec_t
|
|
149
|
-
|
|
147
|
+
NK_INTERNAL void nk_hamming_u32x4_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
148
|
+
nk_b128_vec_t const *target_pops_vec,
|
|
149
|
+
nk_b128_vec_t *result_vec) {
|
|
150
|
+
__m128i dots_u32x4 = dots_vec->xmm;
|
|
150
151
|
__m128i query_u32x4 = __lsx_vreplgr2vr_w((int)query_pop);
|
|
151
|
-
__m128i target_u32x4 =
|
|
152
|
-
|
|
152
|
+
__m128i target_u32x4 = target_pops_vec->xmm;
|
|
153
|
+
result_vec->xmm = __lsx_vsub_w(__lsx_vadd_w(query_u32x4, target_u32x4), __lsx_vslli_w(dots_u32x4, 1));
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
/** @brief Jaccard from_dot: computes 1 − dot / (pop_a + pop_b − dot) for 4 pairs (LSX). */
|
|
156
|
-
NK_INTERNAL void nk_jaccard_f32x4_from_dot_loongsonasx_(nk_b128_vec_t
|
|
157
|
-
nk_b128_vec_t
|
|
158
|
-
|
|
157
|
+
NK_INTERNAL void nk_jaccard_f32x4_from_dot_loongsonasx_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
158
|
+
nk_b128_vec_t const *target_pops_vec,
|
|
159
|
+
nk_b128_vec_t *result_vec) {
|
|
160
|
+
__m128 dot_f32x4 = __lsx_vffint_s_wu(dots_vec->xmm);
|
|
159
161
|
__m128 query_f32x4 = nk_xvreplgr2vr_s_128_((nk_f32_t)query_pop);
|
|
160
|
-
__m128 target_f32x4 = __lsx_vffint_s_wu(
|
|
162
|
+
__m128 target_f32x4 = __lsx_vffint_s_wu(target_pops_vec->xmm);
|
|
161
163
|
__m128 union_f32x4 = __lsx_vfsub_s(__lsx_vfadd_s(query_f32x4, target_f32x4), dot_f32x4);
|
|
162
164
|
|
|
163
165
|
__m128 zero_f32x4 = (__m128)__lsx_vreplgr2vr_w(0);
|
|
@@ -167,7 +169,7 @@ NK_INTERNAL void nk_jaccard_f32x4_from_dot_loongsonasx_(nk_b128_vec_t dots, nk_u
|
|
|
167
169
|
|
|
168
170
|
__m128 ratio_f32x4 = __lsx_vfdiv_s(dot_f32x4, safe_union_f32x4);
|
|
169
171
|
__m128 jaccard_f32x4 = __lsx_vfsub_s(one_f32x4, ratio_f32x4);
|
|
170
|
-
|
|
172
|
+
result_vec->xmm_ps = (__m128)__lsx_vbitsel_v((__m128i)jaccard_f32x4, (__m128i)zero_f32x4, zero_union_mask_u32x4);
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
#pragma endregion Batched Finalizers
|
|
@@ -34,8 +34,7 @@
|
|
|
34
34
|
* nk_jaccard_u1x128_init_neon(&state_first);
|
|
35
35
|
* // ... stream through packed binary vectors ...
|
|
36
36
|
* nk_jaccard_u1x128_finalize_neon(&state_first, &state_second, &state_third, &state_fourth,
|
|
37
|
-
* query_popcount,
|
|
38
|
-
* total_dimensions, &results);
|
|
37
|
+
* query_popcount, &target_popcounts_vec, total_dimensions, &result_vec);
|
|
39
38
|
* @endcode
|
|
40
39
|
*/
|
|
41
40
|
#ifndef NK_SET_NEON_H
|
|
@@ -274,8 +273,8 @@ NK_INTERNAL void nk_jaccard_u1x128_update_neon(nk_jaccard_u1x128_state_neon_t *s
|
|
|
274
273
|
NK_INTERNAL void nk_jaccard_u1x128_finalize_neon( //
|
|
275
274
|
nk_jaccard_u1x128_state_neon_t const *state_a, nk_jaccard_u1x128_state_neon_t const *state_b,
|
|
276
275
|
nk_jaccard_u1x128_state_neon_t const *state_c, nk_jaccard_u1x128_state_neon_t const *state_d,
|
|
277
|
-
nk_f32_t query_popcount,
|
|
278
|
-
|
|
276
|
+
nk_f32_t query_popcount, nk_b128_vec_t const *target_popcounts_vec, nk_size_t total_dimensions,
|
|
277
|
+
nk_b128_vec_t *result_vec) {
|
|
279
278
|
nk_unused_(total_dimensions);
|
|
280
279
|
|
|
281
280
|
// Horizontal sum using pairwise adds instead of `vaddvq_u32` (ADDV).
|
|
@@ -290,11 +289,8 @@ NK_INTERNAL void nk_jaccard_u1x128_finalize_neon( //
|
|
|
290
289
|
float32x4_t intersection_f32x4 = vcvtq_f32_u32(intersection_u32x4);
|
|
291
290
|
|
|
292
291
|
// Compute union using |A ∪ B| = |A| + |B| - |A ∩ B|
|
|
293
|
-
// Build target popcounts vector from two independent halves (avoids serial lane insertion chain).
|
|
294
292
|
float32x4_t query_f32x4 = vdupq_n_f32(query_popcount);
|
|
295
|
-
|
|
296
|
-
float32x2_t targets_cd_f32x2 = vset_lane_f32(target_popcount_d, vdup_n_f32(target_popcount_c), 1);
|
|
297
|
-
float32x4_t targets_f32x4 = vcombine_f32(targets_ab_f32x2, targets_cd_f32x2);
|
|
293
|
+
float32x4_t targets_f32x4 = target_popcounts_vec->f32x4;
|
|
298
294
|
float32x4_t union_f32x4 = vsubq_f32(vaddq_f32(query_f32x4, targets_f32x4), intersection_f32x4);
|
|
299
295
|
|
|
300
296
|
// Handle zero-union edge case (empty vectors → distance = 0.0, matching scipy convention)
|
|
@@ -314,24 +310,24 @@ NK_INTERNAL void nk_jaccard_u1x128_finalize_neon( //
|
|
|
314
310
|
// Compute Jaccard distance = 1 - intersection ÷ union
|
|
315
311
|
float32x4_t ratio_f32x4 = vmulq_f32(intersection_f32x4, union_reciprocal_f32x4);
|
|
316
312
|
float32x4_t jaccard_f32x4 = vsubq_f32(one_f32x4, ratio_f32x4);
|
|
317
|
-
|
|
313
|
+
result_vec->f32x4 = vbslq_f32(zero_union_mask, vdupq_n_f32(0.0f), jaccard_f32x4);
|
|
318
314
|
}
|
|
319
315
|
|
|
320
316
|
/** @brief Hamming from_dot: computes pop_a + pop_b - 2*dot for 4 pairs (NEON). */
|
|
321
|
-
NK_INTERNAL void nk_hamming_u32x4_from_dot_neon_(nk_b128_vec_t
|
|
322
|
-
nk_b128_vec_t *
|
|
323
|
-
uint32x4_t dots_u32x4 =
|
|
317
|
+
NK_INTERNAL void nk_hamming_u32x4_from_dot_neon_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
318
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
319
|
+
uint32x4_t dots_u32x4 = dots_vec->u32x4;
|
|
324
320
|
uint32x4_t query_u32x4 = vdupq_n_u32(query_pop);
|
|
325
|
-
uint32x4_t target_u32x4 =
|
|
326
|
-
|
|
321
|
+
uint32x4_t target_u32x4 = target_pops_vec->u32x4;
|
|
322
|
+
result_vec->u32x4 = vsubq_u32(vaddq_u32(query_u32x4, target_u32x4), vshlq_n_u32(dots_u32x4, 1));
|
|
327
323
|
}
|
|
328
324
|
|
|
329
325
|
/** @brief Jaccard from_dot: computes 1 - dot / (pop_a + pop_b - dot) for 4 pairs (NEON). */
|
|
330
|
-
NK_INTERNAL void nk_jaccard_f32x4_from_dot_neon_(nk_b128_vec_t
|
|
331
|
-
nk_b128_vec_t *
|
|
332
|
-
float32x4_t dot_f32x4 = vcvtq_f32_u32(
|
|
326
|
+
NK_INTERNAL void nk_jaccard_f32x4_from_dot_neon_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
327
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
328
|
+
float32x4_t dot_f32x4 = vcvtq_f32_u32(dots_vec->u32x4);
|
|
333
329
|
float32x4_t query_f32x4 = vdupq_n_f32((nk_f32_t)query_pop);
|
|
334
|
-
float32x4_t target_f32x4 = vcvtq_f32_u32(
|
|
330
|
+
float32x4_t target_f32x4 = vcvtq_f32_u32(target_pops_vec->u32x4);
|
|
335
331
|
float32x4_t union_f32x4 = vsubq_f32(vaddq_f32(query_f32x4, target_f32x4), dot_f32x4);
|
|
336
332
|
|
|
337
333
|
float32x4_t one_f32x4 = vdupq_n_f32(1.0f);
|
|
@@ -343,7 +339,7 @@ NK_INTERNAL void nk_jaccard_f32x4_from_dot_neon_(nk_b128_vec_t dots, nk_u32_t qu
|
|
|
343
339
|
|
|
344
340
|
float32x4_t ratio_f32x4 = vmulq_f32(dot_f32x4, union_reciprocal_f32x4);
|
|
345
341
|
float32x4_t jaccard_f32x4 = vsubq_f32(one_f32x4, ratio_f32x4);
|
|
346
|
-
|
|
342
|
+
result_vec->f32x4 = vbslq_f32(zero_union_mask, vdupq_n_f32(0.0f), jaccard_f32x4);
|
|
347
343
|
}
|
|
348
344
|
|
|
349
345
|
#pragma endregion Stateful Streaming
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
* nk_jaccard_u1x128_init_powervsx(&state_first);
|
|
34
34
|
* // ... stream through packed binary vectors ...
|
|
35
35
|
* nk_jaccard_u1x128_finalize_powervsx(&state_first, &state_second, &state_third, &state_fourth,
|
|
36
|
-
* query_popcount,
|
|
37
|
-
* total_dimensions, &results);
|
|
36
|
+
* query_popcount, &target_popcounts_vec, total_dimensions, &result_vec);
|
|
38
37
|
* @endcode
|
|
39
38
|
*/
|
|
40
39
|
#ifndef NK_SET_POWERVSX_H
|
|
@@ -224,8 +223,8 @@ NK_INTERNAL void nk_jaccard_u1x128_update_powervsx(nk_jaccard_u1x128_state_power
|
|
|
224
223
|
NK_INTERNAL void nk_jaccard_u1x128_finalize_powervsx( //
|
|
225
224
|
nk_jaccard_u1x128_state_powervsx_t const *state_a, nk_jaccard_u1x128_state_powervsx_t const *state_b,
|
|
226
225
|
nk_jaccard_u1x128_state_powervsx_t const *state_c, nk_jaccard_u1x128_state_powervsx_t const *state_d,
|
|
227
|
-
nk_f32_t query_popcount,
|
|
228
|
-
|
|
226
|
+
nk_f32_t query_popcount, nk_b128_vec_t const *target_popcounts_vec, nk_size_t total_dimensions,
|
|
227
|
+
nk_b128_vec_t *result_vec) {
|
|
229
228
|
nk_unused_(total_dimensions);
|
|
230
229
|
|
|
231
230
|
// Transpose-based 4-way horizontal sum of u32x4 intersection counts
|
|
@@ -247,12 +246,7 @@ NK_INTERNAL void nk_jaccard_u1x128_finalize_powervsx( //
|
|
|
247
246
|
vec_add(sum_lane2_u32x4, sum_lane3_u32x4));
|
|
248
247
|
nk_vf32x4_t intersection_f32x4 = vec_ctf(intersection_u32x4, 0);
|
|
249
248
|
|
|
250
|
-
|
|
251
|
-
nk_vf32x4_t targets_f32x4 = vec_splats(0.0f);
|
|
252
|
-
targets_f32x4 = vec_insert(target_popcount_a, targets_f32x4, 0);
|
|
253
|
-
targets_f32x4 = vec_insert(target_popcount_b, targets_f32x4, 1);
|
|
254
|
-
targets_f32x4 = vec_insert(target_popcount_c, targets_f32x4, 2);
|
|
255
|
-
targets_f32x4 = vec_insert(target_popcount_d, targets_f32x4, 3);
|
|
249
|
+
nk_vf32x4_t targets_f32x4 = target_popcounts_vec->vf32x4;
|
|
256
250
|
nk_vf32x4_t query_f32x4 = vec_splats(query_popcount);
|
|
257
251
|
|
|
258
252
|
// Compute union using |A union B| = |A| + |B| - |A intersection B|
|
|
@@ -274,25 +268,25 @@ NK_INTERNAL void nk_jaccard_u1x128_finalize_powervsx( //
|
|
|
274
268
|
// Compute Jaccard distance = 1 - intersection / union
|
|
275
269
|
nk_vf32x4_t ratio_f32x4 = vec_mul(intersection_f32x4, union_reciprocal_f32x4);
|
|
276
270
|
nk_vf32x4_t jaccard_f32x4 = vec_sub(one_f32x4, ratio_f32x4);
|
|
277
|
-
|
|
271
|
+
result_vec->vf32x4 = vec_sel(jaccard_f32x4, zero_f32x4, zero_union_mask_u32x4);
|
|
278
272
|
}
|
|
279
273
|
|
|
280
274
|
/** @brief Hamming from_dot: computes pop_a + pop_b - 2 × dot for 4 pairs (Power VSX). */
|
|
281
|
-
NK_INTERNAL void nk_hamming_u32x4_from_dot_powervsx_(nk_b128_vec_t
|
|
282
|
-
nk_b128_vec_t *
|
|
283
|
-
nk_vu32x4_t dots_u32x4 =
|
|
275
|
+
NK_INTERNAL void nk_hamming_u32x4_from_dot_powervsx_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
276
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
277
|
+
nk_vu32x4_t dots_u32x4 = dots_vec->vu32x4;
|
|
284
278
|
nk_vu32x4_t query_u32x4 = vec_splats(query_pop);
|
|
285
|
-
nk_vu32x4_t target_u32x4 =
|
|
279
|
+
nk_vu32x4_t target_u32x4 = target_pops_vec->vu32x4;
|
|
286
280
|
nk_vu32x4_t two_dots_u32x4 = vec_add(dots_u32x4, dots_u32x4);
|
|
287
|
-
|
|
281
|
+
result_vec->vu32x4 = vec_sub(vec_add(query_u32x4, target_u32x4), two_dots_u32x4);
|
|
288
282
|
}
|
|
289
283
|
|
|
290
284
|
/** @brief Jaccard from_dot: computes 1 - dot / (pop_a + pop_b - dot) for 4 pairs (Power VSX). */
|
|
291
|
-
NK_INTERNAL void nk_jaccard_f32x4_from_dot_powervsx_(nk_b128_vec_t
|
|
292
|
-
nk_b128_vec_t *
|
|
293
|
-
nk_vf32x4_t dot_f32x4 = vec_ctf(
|
|
285
|
+
NK_INTERNAL void nk_jaccard_f32x4_from_dot_powervsx_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
286
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
287
|
+
nk_vf32x4_t dot_f32x4 = vec_ctf(dots_vec->vu32x4, 0);
|
|
294
288
|
nk_vf32x4_t query_f32x4 = vec_splats((nk_f32_t)query_pop);
|
|
295
|
-
nk_vf32x4_t target_f32x4 = vec_ctf(
|
|
289
|
+
nk_vf32x4_t target_f32x4 = vec_ctf(target_pops_vec->vu32x4, 0);
|
|
296
290
|
nk_vf32x4_t union_f32x4 = vec_sub(vec_add(query_f32x4, target_f32x4), dot_f32x4);
|
|
297
291
|
|
|
298
292
|
nk_vf32x4_t one_f32x4 = vec_splats(1.0f);
|
|
@@ -308,7 +302,7 @@ NK_INTERNAL void nk_jaccard_f32x4_from_dot_powervsx_(nk_b128_vec_t dots, nk_u32_
|
|
|
308
302
|
|
|
309
303
|
nk_vf32x4_t ratio_f32x4 = vec_mul(dot_f32x4, union_reciprocal_f32x4);
|
|
310
304
|
nk_vf32x4_t jaccard_f32x4 = vec_sub(one_f32x4, ratio_f32x4);
|
|
311
|
-
|
|
305
|
+
result_vec->vf32x4 = vec_sel(jaccard_f32x4, zero_f32x4, zero_union_mask_u32x4);
|
|
312
306
|
}
|
|
313
307
|
|
|
314
308
|
#if defined(__clang__)
|