usearch 2.26.1 → 2.26.2
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
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
* nk_jaccard_u1x128_init_serial(&state_first);
|
|
24
24
|
* // ... stream through packed binary vectors ...
|
|
25
25
|
* nk_jaccard_u1x128_finalize_serial(&state_first, &state_second, &state_third, &state_fourth,
|
|
26
|
-
* query_popcount,
|
|
27
|
-
* total_dimensions, &results);
|
|
26
|
+
* query_popcount, &target_popcounts_vec, total_dimensions, &result_vec);
|
|
28
27
|
* @endcode
|
|
29
28
|
*/
|
|
30
29
|
#ifndef NK_SET_SERIAL_H
|
|
@@ -99,8 +98,8 @@ NK_INTERNAL void nk_jaccard_u1x128_update_serial(nk_jaccard_u1x128_state_serial_
|
|
|
99
98
|
NK_INTERNAL void nk_jaccard_u1x128_finalize_serial( //
|
|
100
99
|
nk_jaccard_u1x128_state_serial_t const *state_a, nk_jaccard_u1x128_state_serial_t const *state_b,
|
|
101
100
|
nk_jaccard_u1x128_state_serial_t const *state_c, nk_jaccard_u1x128_state_serial_t const *state_d,
|
|
102
|
-
nk_f32_t query_popcount,
|
|
103
|
-
|
|
101
|
+
nk_f32_t query_popcount, nk_b128_vec_t const *target_popcounts_vec, nk_size_t total_dimensions,
|
|
102
|
+
nk_b128_vec_t *result_vec) {
|
|
104
103
|
nk_unused_(total_dimensions);
|
|
105
104
|
|
|
106
105
|
nk_f32_t intersection_a = (nk_f32_t)state_a->intersection_count;
|
|
@@ -108,15 +107,15 @@ NK_INTERNAL void nk_jaccard_u1x128_finalize_serial( //
|
|
|
108
107
|
nk_f32_t intersection_c = (nk_f32_t)state_c->intersection_count;
|
|
109
108
|
nk_f32_t intersection_d = (nk_f32_t)state_d->intersection_count;
|
|
110
109
|
|
|
111
|
-
nk_f32_t union_a = query_popcount +
|
|
112
|
-
nk_f32_t union_b = query_popcount +
|
|
113
|
-
nk_f32_t union_c = query_popcount +
|
|
114
|
-
nk_f32_t union_d = query_popcount +
|
|
110
|
+
nk_f32_t union_a = query_popcount + target_popcounts_vec->f32s[0] - intersection_a;
|
|
111
|
+
nk_f32_t union_b = query_popcount + target_popcounts_vec->f32s[1] - intersection_b;
|
|
112
|
+
nk_f32_t union_c = query_popcount + target_popcounts_vec->f32s[2] - intersection_c;
|
|
113
|
+
nk_f32_t union_d = query_popcount + target_popcounts_vec->f32s[3] - intersection_d;
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
result_vec->f32s[0] = (union_a != 0) ? 1.0f - intersection_a / union_a : 0.0f;
|
|
116
|
+
result_vec->f32s[1] = (union_b != 0) ? 1.0f - intersection_b / union_b : 0.0f;
|
|
117
|
+
result_vec->f32s[2] = (union_c != 0) ? 1.0f - intersection_c / union_c : 0.0f;
|
|
118
|
+
result_vec->f32s[3] = (union_d != 0) ? 1.0f - intersection_d / union_d : 0.0f;
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
typedef struct nk_hamming_u1x128_state_serial_t {
|
|
@@ -150,18 +149,18 @@ NK_INTERNAL void nk_hamming_u1x128_finalize_serial( //
|
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
/** @brief Hamming from_dot: computes pop_a + pop_b - 2*dot for 4 pairs (serial). */
|
|
153
|
-
NK_INTERNAL void nk_hamming_u32x4_from_dot_serial_(nk_b128_vec_t
|
|
154
|
-
nk_b128_vec_t *
|
|
155
|
-
for (int i = 0; i < 4; ++i)
|
|
152
|
+
NK_INTERNAL void nk_hamming_u32x4_from_dot_serial_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
153
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
154
|
+
for (int i = 0; i < 4; ++i) result_vec->u32s[i] = query_pop + target_pops_vec->u32s[i] - 2 * dots_vec->u32s[i];
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
/** @brief Jaccard from_dot: computes 1 - dot / (pop_a + pop_b - dot) for 4 pairs (serial). */
|
|
159
|
-
NK_INTERNAL void nk_jaccard_f32x4_from_dot_serial_(nk_b128_vec_t
|
|
160
|
-
nk_b128_vec_t *
|
|
158
|
+
NK_INTERNAL void nk_jaccard_f32x4_from_dot_serial_(nk_b128_vec_t const *dots_vec, nk_u32_t query_pop,
|
|
159
|
+
nk_b128_vec_t const *target_pops_vec, nk_b128_vec_t *result_vec) {
|
|
161
160
|
for (int i = 0; i < 4; ++i) {
|
|
162
|
-
nk_f32_t dot = (nk_f32_t)
|
|
163
|
-
nk_f32_t union_val = (nk_f32_t)query_pop + (nk_f32_t)
|
|
164
|
-
|
|
161
|
+
nk_f32_t dot = (nk_f32_t)dots_vec->u32s[i];
|
|
162
|
+
nk_f32_t union_val = (nk_f32_t)query_pop + (nk_f32_t)target_pops_vec->u32s[i] - dot;
|
|
163
|
+
result_vec->f32s[i] = (union_val != 0) ? 1.0f - dot / union_val : 0.0f;
|
|
165
164
|
}
|
|
166
165
|
}
|
|
167
166
|
|
|
@@ -202,18 +202,20 @@ NK_PUBLIC void nk_jaccard_u16_v128relaxed(nk_u16_t const *a, nk_u16_t const *b,
|
|
|
202
202
|
#pragma region Binary Sets from Dot
|
|
203
203
|
|
|
204
204
|
NK_INTERNAL void nk_hamming_u32x4_from_dot_v128relaxed_( //
|
|
205
|
-
nk_b128_vec_t
|
|
206
|
-
|
|
205
|
+
nk_b128_vec_t const *dots_vec, nk_u32_t query_pop, nk_b128_vec_t const *target_pops_vec,
|
|
206
|
+
nk_b128_vec_t *result_vec) {
|
|
207
|
+
v128_t dots_u32x4 = dots_vec->v128;
|
|
207
208
|
v128_t query_u32x4 = wasm_u32x4_splat(query_pop);
|
|
208
|
-
v128_t target_u32x4 =
|
|
209
|
-
|
|
209
|
+
v128_t target_u32x4 = target_pops_vec->v128;
|
|
210
|
+
result_vec->v128 = wasm_i32x4_sub(wasm_i32x4_add(query_u32x4, target_u32x4), wasm_i32x4_shl(dots_u32x4, 1));
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
NK_INTERNAL void nk_jaccard_f32x4_from_dot_v128relaxed_( //
|
|
213
|
-
nk_b128_vec_t
|
|
214
|
-
|
|
214
|
+
nk_b128_vec_t const *dots_vec, nk_u32_t query_pop, nk_b128_vec_t const *target_pops_vec,
|
|
215
|
+
nk_b128_vec_t *result_vec) {
|
|
216
|
+
v128_t dot_f32x4 = wasm_f32x4_convert_u32x4(dots_vec->v128);
|
|
215
217
|
v128_t query_f32x4 = wasm_f32x4_splat((nk_f32_t)query_pop);
|
|
216
|
-
v128_t target_f32x4 = wasm_f32x4_convert_u32x4(
|
|
218
|
+
v128_t target_f32x4 = wasm_f32x4_convert_u32x4(target_pops_vec->v128);
|
|
217
219
|
v128_t union_f32x4 = wasm_f32x4_sub(wasm_f32x4_add(query_f32x4, target_f32x4), dot_f32x4);
|
|
218
220
|
|
|
219
221
|
v128_t zero_f32x4 = wasm_f32x4_splat(0.0f);
|
|
@@ -223,7 +225,7 @@ NK_INTERNAL void nk_jaccard_f32x4_from_dot_v128relaxed_( //
|
|
|
223
225
|
|
|
224
226
|
v128_t ratio_f32x4 = wasm_f32x4_div(dot_f32x4, safe_union_f32x4);
|
|
225
227
|
v128_t jaccard_f32x4 = wasm_f32x4_sub(one_f32x4, ratio_f32x4);
|
|
226
|
-
|
|
228
|
+
result_vec->v128 = wasm_i32x4_relaxed_laneselect(zero_f32x4, jaccard_f32x4, zero_mask_u32x4);
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
#pragma endregion Binary Sets from Dot
|
|
@@ -38,15 +38,15 @@ def sparse_dot(a_indices: np.ndarray, a_weights: np.ndarray,
|
|
|
38
38
|
| `u16` | `u64` | 16-bit index intersection count |
|
|
39
39
|
| `u32` | `u64` | 32-bit index intersection count |
|
|
40
40
|
| `u64` | `u64` | 64-bit index intersection count |
|
|
41
|
-
| `u32+f32` | `
|
|
41
|
+
| `u32+f32` | `f64` | Sparse dot with 32-bit indices, f32 weights |
|
|
42
42
|
| `u16+bf16` | `f32` | Sparse dot with 16-bit indices, bf16 weights |
|
|
43
43
|
|
|
44
44
|
## Optimizations
|
|
45
45
|
|
|
46
46
|
### Adaptive Merge vs Galloping Search
|
|
47
47
|
|
|
48
|
-
`nk_sparse_intersect_u32_serial` selects between linear merge and galloping (exponential) search based on length ratio: when `longer_length
|
|
49
|
-
Linear merge advances two pointers in lockstep at $O(|A| + |B|)$ using branch-free conditional increments: `i += ai
|
|
48
|
+
`nk_sparse_intersect_u32_serial` selects between linear merge and galloping (exponential) search based on length ratio: when `longer_length >= 64 * shorter_length`, galloping search over the longer array is used.
|
|
49
|
+
Linear merge advances two pointers in lockstep at $O(|A| + |B|)$ using branch-free conditional increments: `i += ai <= bj; j += ai >= bj` — no branch misprediction penalty.
|
|
50
50
|
Galloping binary-searches the longer array for each element of the shorter at $O(|A| \cdot \log |B|)$: an exponential probe doubles the search range until the target is bracketed, then binary search narrows within.
|
|
51
51
|
The crossover at 64x length ratio balances the per-element cost of binary search ($\log_2 |B|$ comparisons) against linear scan's single comparison per advance — the threshold was chosen empirically, as cache locality favors linear merge at moderate ratios.
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ The crossover at 64x length ratio balances the per-element cost of binary search
|
|
|
56
56
|
The rotation approach uses `_mm512_shuffle_epi32` with permutation constants (`_MM_PERM_ADCB`, etc.) to cycle elements through comparison positions — contending for port 5 (~1cy per shuffle, ~3cy for `_mm512_alignr_epi32`).
|
|
57
57
|
Match counts are extracted via `_mm_popcnt_u32` on the comparison masks, accumulating intersection size without materializing matched elements.
|
|
58
58
|
Before each 16x16 comparison block, a fast overlap check (`a_max < b_min || b_max < a_min`) skips non-overlapping register loads entirely — critical for sparse workloads where most pairs have disjoint index ranges.
|
|
59
|
-
No native `_mm512_2intersect_epi16` instruction exists in any x86 ISA
|
|
59
|
+
No native `_mm512_2intersect_epi16` instruction exists in any x86 ISA, so `nk_sparse_intersect_u16_icelake` runs the same rotate-and-compare pattern over 32 UInt16 lanes with `_mm512_alignr_epi32`.
|
|
60
60
|
|
|
61
61
|
### VP2INTERSECT on AMD Turin
|
|
62
62
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
extern "C" {
|
|
17
17
|
#endif
|
|
18
18
|
|
|
19
|
-
#define
|
|
19
|
+
#define nk_define_sparse_intersect_helpers_(input_type) \
|
|
20
20
|
NK_INTERNAL nk_size_t nk_sparse_intersect_##input_type##_galloping_search_( \
|
|
21
21
|
nk_##input_type##_t const *array, nk_size_t start, nk_size_t length, nk_##input_type##_t val) { \
|
|
22
22
|
nk_size_t low = start; \
|
|
@@ -48,7 +48,9 @@ extern "C" {
|
|
|
48
48
|
j += ai >= bj; \
|
|
49
49
|
} \
|
|
50
50
|
return intersection_size; \
|
|
51
|
-
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#define nk_define_sparse_intersect_(input_type) \
|
|
52
54
|
NK_PUBLIC void nk_sparse_intersect_##input_type##_serial( \
|
|
53
55
|
nk_##input_type##_t const *shorter, nk_##input_type##_t const *longer, nk_size_t shorter_length, \
|
|
54
56
|
nk_size_t longer_length, nk_##input_type##_t *result, nk_size_t *count) { \
|
|
@@ -103,7 +105,12 @@ extern "C" {
|
|
|
103
105
|
*product = weights_product; \
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
nk_define_sparse_intersect_helpers_(u16)
|
|
109
|
+
nk_define_sparse_intersect_helpers_(u32)
|
|
110
|
+
nk_define_sparse_intersect_helpers_(u64)
|
|
111
|
+
|
|
112
|
+
/* Keep the serial instantiations below actually scalar, regardless of build type. The search helpers
|
|
113
|
+
* above stay outside: they are `always_inline`, which clang refuses to combine with a pushed `noinline`.
|
|
107
114
|
* See dots/serial.h for rationale. */
|
|
108
115
|
#if defined(__clang__)
|
|
109
116
|
#pragma clang attribute push(__attribute__((noinline)), apply_to = function)
|
|
@@ -43,20 +43,22 @@ def angular(a: np.ndarray, b: np.ndarray) -> float:
|
|
|
43
43
|
|
|
44
44
|
## Input & Output Types
|
|
45
45
|
|
|
46
|
-
| Input Type | Output Type | Description
|
|
47
|
-
| ---------- | ----------- |
|
|
48
|
-
| `f64` | `f64` | 64-bit IEEE 754 double precision
|
|
49
|
-
| `f32` | `
|
|
50
|
-
| `f16` | `f32` | 16-bit IEEE 754 half precision, widened output
|
|
51
|
-
| `bf16` | `f32` | 16-bit brain float, widened output
|
|
52
|
-
| `e5m2` | `f32` | 8-bit Float8: 5 exponent, 2 mantissa bits
|
|
53
|
-
| `e4m3` | `f32` | 8-bit Float8: 4 exponent, 3 mantissa bits
|
|
54
|
-
| `e3m2` | `f32` | 8-bit MX format: 3 exponent, 2 mantissa bits
|
|
55
|
-
| `e2m3` | `f32` | 8-bit MX format: 2 exponent, 3 mantissa bits
|
|
56
|
-
| `i8` | `f32` | 8-bit signed integers
|
|
57
|
-
| `u8` | `f32` | 8-bit unsigned integers
|
|
58
|
-
| `i4` | `f32` | 4-bit signed integers, packed nibble pairs
|
|
59
|
-
| `u4` | `f32` | 4-bit unsigned integers, packed nibble pairs
|
|
46
|
+
| Input Type | Output Type | Description |
|
|
47
|
+
| ---------- | ----------- | ------------------------------------------------ |
|
|
48
|
+
| `f64` | `f64` | 64-bit IEEE 754 double precision |
|
|
49
|
+
| `f32` | `f64` | 32-bit IEEE 754 single precision, widened output |
|
|
50
|
+
| `f16` | `f32` | 16-bit IEEE 754 half precision, widened output |
|
|
51
|
+
| `bf16` | `f32` | 16-bit brain float, widened output |
|
|
52
|
+
| `e5m2` | `f32` | 8-bit Float8: 5 exponent, 2 mantissa bits |
|
|
53
|
+
| `e4m3` | `f32` | 8-bit Float8: 4 exponent, 3 mantissa bits |
|
|
54
|
+
| `e3m2` | `f32` | 8-bit MX format: 3 exponent, 2 mantissa bits |
|
|
55
|
+
| `e2m3` | `f32` | 8-bit MX format: 2 exponent, 3 mantissa bits |
|
|
56
|
+
| `i8` | `f32` | 8-bit signed integers |
|
|
57
|
+
| `u8` | `f32` | 8-bit unsigned integers |
|
|
58
|
+
| `i4` | `f32` | 4-bit signed integers, packed nibble pairs |
|
|
59
|
+
| `u4` | `f32` | 4-bit unsigned integers, packed nibble pairs |
|
|
60
|
+
|
|
61
|
+
The integer `nk_sqeuclidean_*` kernels are the exception: `i8`, `u8`, `i4`, and `u4` return an exact `u32` count of squared differences, while `nk_euclidean_*` and `nk_angular_*` return `f32`.
|
|
60
62
|
|
|
61
63
|
## Optimizations
|
|
62
64
|
|
|
@@ -69,12 +71,11 @@ The single-pass design is essential because reading two vectors of length $n$ on
|
|
|
69
71
|
|
|
70
72
|
### Reciprocal Square Root with Newton-Raphson Refinement
|
|
71
73
|
|
|
72
|
-
`
|
|
74
|
+
`nk_angular_f16_haswell`, `nk_angular_i8_haswell`, `nk_angular_e4m3_skylake`, `nk_angular_f32_neon`, `nk_angular_f64_neon` compute the final normalization via in-hardware reciprocal square root estimates refined by Newton-Raphson iteration.
|
|
73
75
|
The iteration formula is $x_{n+1} = x_n \cdot (3 - d \cdot x_n^2) / 2$, where $d$ is the value whose reciprocal square root is needed.
|
|
74
|
-
NEON `
|
|
75
|
-
Haswell `
|
|
76
|
-
|
|
77
|
-
This reciprocal square root is needed for both euclidean distance ($\sqrt{d}$ via $d \cdot \text{rsqrt}(d)$) and angular distance ($1/\sqrt{a^2} \cdot 1/\sqrt{b^2}$).
|
|
76
|
+
NEON `vrsqrte_f32` + `vrsqrts_f32` runs two refinement steps for Float32, and `vrsqrteq_f64` + `vrsqrtsq_f64` runs three for Float64.
|
|
77
|
+
Haswell `VRSQRTPS` starts from ~12 bits (1.5 x $2^{-12}$ relative error) and one Newton-Raphson step roughly doubles that; Skylake `VRSQRT14PS` starts from $2^{-14}$ and refines the same way.
|
|
78
|
+
The Float32 and Float64 angular kernels on x86 do not take this path at all: they accumulate in Float64 and finish with an exact `VSQRTPD`, because a refined 14-bit estimate is still far short of a 52-bit mantissa.
|
|
78
79
|
|
|
79
80
|
### Absolute Differences for Integer Types
|
|
80
81
|
|
|
@@ -90,12 +91,12 @@ abs_diff = _mm256_or_si256(_mm256_subs_epu8(bias_a, bias_b), _mm256_subs_epu8(bi
|
|
|
90
91
|
For unsigned `u8`, the same saturating subtract trick works without the XOR bias.
|
|
91
92
|
The absolute differences are then zero-extended via `VPUNPCKLBW`/`VPUNPCKHBW` (1 cycle, cheaper than `VPMOVZXBW`) and squared+accumulated via `VPMADDWD`, which computes $d_i^2 + d_{i+1}^2$ in one instruction.
|
|
92
93
|
|
|
93
|
-
### Masked
|
|
94
|
+
### Masked Dot2 Compensation on Skylake
|
|
94
95
|
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
`nk_angular_f64_skylake` and `nk_angular_f64_haswell` run the Ogita-Rump-Oishi Dot2 error-free transformation on the cross-product accumulator, which is the only one of the three that can suffer cancellation; the two self-products $\|a\|^2$ and $\|b\|^2$ have no negative terms and use a plain FMA.
|
|
97
|
+
Each step recovers the rounding error of both the product and the sum, and folds them into a separate compensation accumulator that is added back once at the end.
|
|
98
|
+
Tail elements are handled by a masked load that zero-fills the inactive lanes, so the same compensated body runs on the final partial iteration — no scalar tail loop that would lose the error tracking.
|
|
99
|
+
`nk_sqeuclidean_f64_skylake` needs none of this: squared differences are non-negative, so a plain `VFMADD231PD` chain suffices.
|
|
99
100
|
|
|
100
101
|
## Performance
|
|
101
102
|
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
#include "numkong/dot/alder.h" // VEX compat macros + dpbusd helpers
|
|
32
32
|
#include "numkong/scalar/haswell.h" // `nk_f32_sqrt_haswell`
|
|
33
33
|
#include "numkong/reduce/haswell.h"
|
|
34
|
-
#include "numkong/
|
|
34
|
+
#include "numkong/spatial/haswell.h" // `nk_angular_normalize_f32_haswell_`
|
|
35
|
+
#include "numkong/cast/serial.h" // `nk_partial_load_b8x32_serial_`
|
|
35
36
|
|
|
36
37
|
#if defined(__cplusplus)
|
|
37
38
|
extern "C" {
|
|
@@ -55,91 +55,99 @@ NK_INTERNAL __m128 nk_rsqrt_f32x4_haswell_(__m128 x) {
|
|
|
55
55
|
NK_INTERNAL __m128 nk_sqrt_f32x4_haswell_(__m128 x) { return _mm_sqrt_ps(_mm_max_ps(x, _mm_setzero_ps())); }
|
|
56
56
|
|
|
57
57
|
/** @brief Angular from_dot: computes 1 − dot × rsqrt(query_sumsq × target_sumsq) for 4 pairs. */
|
|
58
|
-
NK_INTERNAL void nk_angular_through_f32_from_dot_haswell_(nk_b128_vec_t
|
|
59
|
-
nk_b128_vec_t
|
|
60
|
-
|
|
58
|
+
NK_INTERNAL void nk_angular_through_f32_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_f32_t query_sumsq,
|
|
59
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
60
|
+
nk_b128_vec_t *result_vec) {
|
|
61
|
+
__m128 dots_f32x4 = dots_vec->xmm_ps;
|
|
61
62
|
__m128 query_sumsq_f32x4 = _mm_set1_ps(query_sumsq);
|
|
62
|
-
__m128 products_f32x4 = _mm_mul_ps(query_sumsq_f32x4,
|
|
63
|
+
__m128 products_f32x4 = _mm_mul_ps(query_sumsq_f32x4, target_sumsqs_vec->xmm_ps);
|
|
63
64
|
__m128 rsqrt_f32x4 = nk_rsqrt_f32x4_haswell_(products_f32x4);
|
|
64
65
|
__m128 normalized_f32x4 = _mm_mul_ps(dots_f32x4, rsqrt_f32x4);
|
|
65
66
|
__m128 angular_f32x4 = _mm_sub_ps(_mm_set1_ps(1.0f), normalized_f32x4);
|
|
66
|
-
|
|
67
|
+
result_vec->xmm_ps = _mm_max_ps(angular_f32x4, _mm_setzero_ps());
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/** @brief Euclidean from_dot: computes √(query_sumsq + target_sumsq − 2 × dot) for 4 pairs. */
|
|
70
|
-
NK_INTERNAL void nk_euclidean_through_f32_from_dot_haswell_(nk_b128_vec_t
|
|
71
|
-
nk_b128_vec_t
|
|
72
|
-
|
|
71
|
+
NK_INTERNAL void nk_euclidean_through_f32_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_f32_t query_sumsq,
|
|
72
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
73
|
+
nk_b128_vec_t *result_vec) {
|
|
74
|
+
__m128 dots_f32x4 = dots_vec->xmm_ps;
|
|
73
75
|
__m128 query_sumsq_f32x4 = _mm_set1_ps(query_sumsq);
|
|
74
|
-
__m128 sum_sq_f32x4 = _mm_add_ps(query_sumsq_f32x4,
|
|
76
|
+
__m128 sum_sq_f32x4 = _mm_add_ps(query_sumsq_f32x4, target_sumsqs_vec->xmm_ps);
|
|
75
77
|
__m128 dist_sq_f32x4 = _mm_fnmadd_ps(_mm_set1_ps(2.0f), dots_f32x4, sum_sq_f32x4);
|
|
76
|
-
|
|
78
|
+
result_vec->xmm_ps = nk_sqrt_f32x4_haswell_(dist_sq_f32x4);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/** @brief Angular from_dot for native f64: 1 − dot / √(query_sumsq × target_sumsq) for 4 pairs. */
|
|
80
|
-
NK_INTERNAL void nk_angular_through_f64_from_dot_haswell_(nk_b256_vec_t
|
|
81
|
-
nk_b256_vec_t
|
|
82
|
-
|
|
82
|
+
NK_INTERNAL void nk_angular_through_f64_from_dot_haswell_(nk_b256_vec_t const *dots_vec, nk_f64_t query_sumsq,
|
|
83
|
+
nk_b256_vec_t const *target_sumsqs_vec,
|
|
84
|
+
nk_b256_vec_t *result_vec) {
|
|
85
|
+
__m256d dots_f64x4 = dots_vec->ymm_pd;
|
|
83
86
|
__m256d query_sumsq_f64x4 = _mm256_set1_pd(query_sumsq);
|
|
84
|
-
__m256d products_f64x4 = _mm256_mul_pd(query_sumsq_f64x4,
|
|
87
|
+
__m256d products_f64x4 = _mm256_mul_pd(query_sumsq_f64x4, target_sumsqs_vec->ymm_pd);
|
|
85
88
|
__m256d sqrt_products_f64x4 = _mm256_sqrt_pd(products_f64x4);
|
|
86
89
|
__m256d normalized_f64x4 = _mm256_div_pd(dots_f64x4, sqrt_products_f64x4);
|
|
87
90
|
__m256d angular_f64x4 = _mm256_sub_pd(_mm256_set1_pd(1.0), normalized_f64x4);
|
|
88
|
-
|
|
91
|
+
result_vec->ymm_pd = _mm256_max_pd(angular_f64x4, _mm256_setzero_pd());
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
/** @brief Euclidean from_dot for native f64: √(query_sumsq + target_sumsq − 2 × dot) for 4 pairs. */
|
|
92
|
-
NK_INTERNAL void nk_euclidean_through_f64_from_dot_haswell_(nk_b256_vec_t
|
|
93
|
-
nk_b256_vec_t
|
|
94
|
-
|
|
95
|
+
NK_INTERNAL void nk_euclidean_through_f64_from_dot_haswell_(nk_b256_vec_t const *dots_vec, nk_f64_t query_sumsq,
|
|
96
|
+
nk_b256_vec_t const *target_sumsqs_vec,
|
|
97
|
+
nk_b256_vec_t *result_vec) {
|
|
98
|
+
__m256d dots_f64x4 = dots_vec->ymm_pd;
|
|
95
99
|
__m256d query_sumsq_f64x4 = _mm256_set1_pd(query_sumsq);
|
|
96
|
-
__m256d sum_sq_f64x4 = _mm256_add_pd(query_sumsq_f64x4,
|
|
100
|
+
__m256d sum_sq_f64x4 = _mm256_add_pd(query_sumsq_f64x4, target_sumsqs_vec->ymm_pd);
|
|
97
101
|
__m256d dist_sq_f64x4 = _mm256_fnmadd_pd(_mm256_set1_pd(2.0), dots_f64x4, sum_sq_f64x4);
|
|
98
|
-
|
|
102
|
+
result_vec->ymm_pd = _mm256_sqrt_pd(_mm256_max_pd(dist_sq_f64x4, _mm256_setzero_pd()));
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
/** @brief Angular from_dot for i32 accumulators: cast to f32, rsqrt+NR, clamp. 4 pairs. */
|
|
102
|
-
NK_INTERNAL void nk_angular_through_i32_from_dot_haswell_(nk_b128_vec_t
|
|
103
|
-
nk_b128_vec_t
|
|
104
|
-
|
|
106
|
+
NK_INTERNAL void nk_angular_through_i32_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_i32_t query_sumsq,
|
|
107
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
108
|
+
nk_b128_vec_t *result_vec) {
|
|
109
|
+
__m128 dots_f32x4 = _mm_cvtepi32_ps(dots_vec->xmm);
|
|
105
110
|
__m128 query_sumsq_f32x4 = _mm_set1_ps((nk_f32_t)query_sumsq);
|
|
106
|
-
__m128 products_f32x4 = _mm_mul_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(
|
|
111
|
+
__m128 products_f32x4 = _mm_mul_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(target_sumsqs_vec->xmm));
|
|
107
112
|
__m128 rsqrt_f32x4 = nk_rsqrt_f32x4_haswell_(products_f32x4);
|
|
108
113
|
__m128 normalized_f32x4 = _mm_mul_ps(dots_f32x4, rsqrt_f32x4);
|
|
109
114
|
__m128 angular_f32x4 = _mm_sub_ps(_mm_set1_ps(1.0f), normalized_f32x4);
|
|
110
|
-
|
|
115
|
+
result_vec->xmm_ps = _mm_max_ps(angular_f32x4, _mm_setzero_ps());
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
/** @brief Euclidean from_dot for i32 accumulators: cast to f32, then √(a² + b² − 2ab). 4 pairs. */
|
|
114
|
-
NK_INTERNAL void nk_euclidean_through_i32_from_dot_haswell_(nk_b128_vec_t
|
|
115
|
-
nk_b128_vec_t
|
|
116
|
-
|
|
119
|
+
NK_INTERNAL void nk_euclidean_through_i32_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_i32_t query_sumsq,
|
|
120
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
121
|
+
nk_b128_vec_t *result_vec) {
|
|
122
|
+
__m128 dots_f32x4 = _mm_cvtepi32_ps(dots_vec->xmm);
|
|
117
123
|
__m128 query_sumsq_f32x4 = _mm_set1_ps((nk_f32_t)query_sumsq);
|
|
118
|
-
__m128 sum_sq_f32x4 = _mm_add_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(
|
|
124
|
+
__m128 sum_sq_f32x4 = _mm_add_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(target_sumsqs_vec->xmm));
|
|
119
125
|
__m128 dist_sq_f32x4 = _mm_fnmadd_ps(_mm_set1_ps(2.0f), dots_f32x4, sum_sq_f32x4);
|
|
120
|
-
|
|
126
|
+
result_vec->xmm_ps = nk_sqrt_f32x4_haswell_(dist_sq_f32x4);
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
/** @brief Angular from_dot for u32 accumulators: cast to f32, rsqrt+NR, clamp. 4 pairs. */
|
|
124
|
-
NK_INTERNAL void nk_angular_through_u32_from_dot_haswell_(nk_b128_vec_t
|
|
125
|
-
nk_b128_vec_t
|
|
126
|
-
|
|
130
|
+
NK_INTERNAL void nk_angular_through_u32_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_u32_t query_sumsq,
|
|
131
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
132
|
+
nk_b128_vec_t *result_vec) {
|
|
133
|
+
__m128 dots_f32x4 = _mm_cvtepi32_ps(dots_vec->xmm);
|
|
127
134
|
__m128 query_sumsq_f32x4 = _mm_set1_ps((nk_f32_t)query_sumsq);
|
|
128
|
-
__m128 products_f32x4 = _mm_mul_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(
|
|
135
|
+
__m128 products_f32x4 = _mm_mul_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(target_sumsqs_vec->xmm));
|
|
129
136
|
__m128 rsqrt_f32x4 = nk_rsqrt_f32x4_haswell_(products_f32x4);
|
|
130
137
|
__m128 normalized_f32x4 = _mm_mul_ps(dots_f32x4, rsqrt_f32x4);
|
|
131
138
|
__m128 angular_f32x4 = _mm_sub_ps(_mm_set1_ps(1.0f), normalized_f32x4);
|
|
132
|
-
|
|
139
|
+
result_vec->xmm_ps = _mm_max_ps(angular_f32x4, _mm_setzero_ps());
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
/** @brief Euclidean from_dot for u32 accumulators: cast to f32, then √(a² + b² − 2ab). 4 pairs. */
|
|
136
|
-
NK_INTERNAL void nk_euclidean_through_u32_from_dot_haswell_(nk_b128_vec_t
|
|
137
|
-
nk_b128_vec_t
|
|
138
|
-
|
|
143
|
+
NK_INTERNAL void nk_euclidean_through_u32_from_dot_haswell_(nk_b128_vec_t const *dots_vec, nk_u32_t query_sumsq,
|
|
144
|
+
nk_b128_vec_t const *target_sumsqs_vec,
|
|
145
|
+
nk_b128_vec_t *result_vec) {
|
|
146
|
+
__m128 dots_f32x4 = _mm_cvtepi32_ps(dots_vec->xmm);
|
|
139
147
|
__m128 query_sumsq_f32x4 = _mm_set1_ps((nk_f32_t)query_sumsq);
|
|
140
|
-
__m128 sum_sq_f32x4 = _mm_add_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(
|
|
148
|
+
__m128 sum_sq_f32x4 = _mm_add_ps(query_sumsq_f32x4, _mm_cvtepi32_ps(target_sumsqs_vec->xmm));
|
|
141
149
|
__m128 dist_sq_f32x4 = _mm_fnmadd_ps(_mm_set1_ps(2.0f), dots_f32x4, sum_sq_f32x4);
|
|
142
|
-
|
|
150
|
+
result_vec->xmm_ps = nk_sqrt_f32x4_haswell_(dist_sq_f32x4);
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
NK_INTERNAL nk_f64_t nk_angular_normalize_f64_haswell_(nk_f64_t ab, nk_f64_t a2, nk_f64_t b2) {
|
|
@@ -437,7 +437,8 @@ nk_angular_i4_icelake_cycle:
|
|
|
437
437
|
nk_i64_t ax_sum = _mm512_reduce_add_epi64(ax_sum_i64x8);
|
|
438
438
|
nk_i64_t bx_sum = _mm512_reduce_add_epi64(bx_sum_i64x8);
|
|
439
439
|
nk_i32_t ab_raw = _mm512_reduce_add_epi32(ab_i32x16);
|
|
440
|
-
|
|
440
|
+
// Accumulate the bias correction in i64: `64 * n` and `8 * (∑ax + ∑bx)` overflow i32 around n ≈ 2^25.
|
|
441
|
+
nk_i64_t ab = (nk_i64_t)ab_raw - 8 * (ax_sum + bx_sum) + 64 * (nk_i64_t)n;
|
|
441
442
|
|
|
442
443
|
nk_size_t n_bytes_total = nk_size_divide_round_up_(n, 2);
|
|
443
444
|
nk_i32_t norm_excess = 128 * (nk_i32_t)(nk_size_round_up_to_multiple_(n_bytes_total, 64) - n_bytes_total);
|