usearch 2.26.0 → 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 +2 -2
- package/cpp/test.cpp +66 -3
- package/include/usearch/index.hpp +9 -1
- package/include/usearch/index_dense.hpp +16 -5
- 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
|
@@ -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__)
|
|
@@ -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);
|