usearch 2.8.1 → 2.8.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/CITATION.cff +1 -1
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/conanfile.py +1 -1
- package/csharp/nuget/nuget-package.props +1 -1
- package/include/usearch/index.hpp +1 -1
- package/package.json +1 -1
- package/simsimd/.github/workflows/prerelease.yml +20 -11
- package/simsimd/.github/workflows/release.yml +24 -8
- package/simsimd/CMakeLists.txt +2 -2
- package/simsimd/README.md +1 -1
- package/simsimd/cpp/bench.cxx +11 -6
- package/simsimd/include/simsimd/binary.h +36 -22
- package/simsimd/include/simsimd/probability.h +94 -70
- package/simsimd/include/simsimd/simsimd.h +30 -21
- package/simsimd/include/simsimd/spatial.h +199 -117
- package/simsimd/package-ci.json +10 -0
- package/simsimd/package.json +1 -5
- package/simsimd/python/lib.c +1 -0
- package/simsimd/python/test.py +47 -22
- package/wasmer.toml +1 -1
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
a2 += ai * ai; \
|
|
60
60
|
b2 += bi * bi; \
|
|
61
61
|
} \
|
|
62
|
-
return ab != 0 ? 1 - ab * SIMSIMD_RSQRT(a2) * SIMSIMD_RSQRT(b2) : 1;
|
|
62
|
+
return ab != 0 ? (1 - ab * SIMSIMD_RSQRT(a2) * SIMSIMD_RSQRT(b2)) : 1; \
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
#ifdef __cplusplus
|
|
@@ -650,31 +650,31 @@ __attribute__((target("avx2"))) //
|
|
|
650
650
|
inline static simsimd_f32_t
|
|
651
651
|
simsimd_avx2_i8_cos(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n) {
|
|
652
652
|
|
|
653
|
-
__m256i ab_high_vec = _mm256_setzero_si256();
|
|
654
653
|
__m256i ab_low_vec = _mm256_setzero_si256();
|
|
655
|
-
__m256i
|
|
654
|
+
__m256i ab_high_vec = _mm256_setzero_si256();
|
|
656
655
|
__m256i a2_low_vec = _mm256_setzero_si256();
|
|
657
|
-
__m256i
|
|
656
|
+
__m256i a2_high_vec = _mm256_setzero_si256();
|
|
658
657
|
__m256i b2_low_vec = _mm256_setzero_si256();
|
|
658
|
+
__m256i b2_high_vec = _mm256_setzero_si256();
|
|
659
659
|
|
|
660
660
|
simsimd_size_t i = 0;
|
|
661
661
|
for (; i + 32 <= n; i += 32) {
|
|
662
662
|
__m256i a_vec = _mm256_loadu_si256((__m256i const*)(a + i));
|
|
663
663
|
__m256i b_vec = _mm256_loadu_si256((__m256i const*)(b + i));
|
|
664
664
|
|
|
665
|
-
// Unpack int8 to
|
|
666
|
-
__m256i
|
|
667
|
-
__m256i
|
|
668
|
-
__m256i
|
|
669
|
-
__m256i
|
|
665
|
+
// Unpack int8 to int16
|
|
666
|
+
__m256i a_low_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(a_vec, 0));
|
|
667
|
+
__m256i a_high_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(a_vec, 1));
|
|
668
|
+
__m256i b_low_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(b_vec, 0));
|
|
669
|
+
__m256i b_high_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(b_vec, 1));
|
|
670
670
|
|
|
671
|
-
// Multiply and accumulate
|
|
672
|
-
ab_low_vec = _mm256_add_epi32(ab_low_vec,
|
|
673
|
-
ab_high_vec = _mm256_add_epi32(ab_high_vec,
|
|
674
|
-
a2_low_vec = _mm256_add_epi32(a2_low_vec,
|
|
675
|
-
a2_high_vec = _mm256_add_epi32(a2_high_vec,
|
|
676
|
-
b2_low_vec = _mm256_add_epi32(b2_low_vec,
|
|
677
|
-
b2_high_vec = _mm256_add_epi32(b2_high_vec,
|
|
671
|
+
// Multiply and accumulate at int16 level, accumulate at int32 level
|
|
672
|
+
ab_low_vec = _mm256_add_epi32(ab_low_vec, _mm256_madd_epi16(a_low_16, b_low_16));
|
|
673
|
+
ab_high_vec = _mm256_add_epi32(ab_high_vec, _mm256_madd_epi16(a_high_16, b_high_16));
|
|
674
|
+
a2_low_vec = _mm256_add_epi32(a2_low_vec, _mm256_madd_epi16(a_low_16, a_low_16));
|
|
675
|
+
a2_high_vec = _mm256_add_epi32(a2_high_vec, _mm256_madd_epi16(a_high_16, a_high_16));
|
|
676
|
+
b2_low_vec = _mm256_add_epi32(b2_low_vec, _mm256_madd_epi16(b_low_16, b_low_16));
|
|
677
|
+
b2_high_vec = _mm256_add_epi32(b2_high_vec, _mm256_madd_epi16(b_high_16, b_high_16));
|
|
678
678
|
}
|
|
679
679
|
|
|
680
680
|
// Horizontal sum across the 256-bit register
|
|
@@ -704,13 +704,15 @@ simsimd_avx2_i8_cos(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t
|
|
|
704
704
|
ab += ai * bi, a2 += ai * ai, b2 += bi * bi;
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
-
//
|
|
707
|
+
// Compute the reciprocal of the square roots
|
|
708
708
|
__m128 a2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)a2));
|
|
709
709
|
__m128 b2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)b2));
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
710
|
+
|
|
711
|
+
// Compute cosine similarity: ab / sqrt(a2 * b2)
|
|
712
|
+
__m128 denom = _mm_mul_ss(a2_sqrt_recip, b2_sqrt_recip); // Reciprocal of sqrt(a2 * b2)
|
|
713
|
+
__m128 result = _mm_mul_ss(_mm_set_ss((float)ab), denom); // ab * reciprocal of sqrt(a2 * b2)
|
|
714
|
+
|
|
715
|
+
return ab != 0 ? 1 - _mm_cvtss_f32(result) : 0; // Extract the final result
|
|
714
716
|
}
|
|
715
717
|
|
|
716
718
|
__attribute__((target("avx2"))) //
|
|
@@ -730,61 +732,92 @@ simsimd_avx2_i8_ip(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t
|
|
|
730
732
|
*
|
|
731
733
|
* - Implements: L2 squared, inner product, cosine similarity.
|
|
732
734
|
* - Uses `f32` for storage and `f32` for accumulation.
|
|
733
|
-
* - Requires compiler capabilities: avx512f, avx512vl.
|
|
735
|
+
* - Requires compiler capabilities: avx512f, avx512vl, bmi2.
|
|
734
736
|
*/
|
|
735
737
|
|
|
736
|
-
__attribute__((target("avx512f,avx512vl"))) //
|
|
738
|
+
__attribute__((target("avx512f,avx512vl,bmi2"))) //
|
|
737
739
|
inline static simsimd_f32_t
|
|
738
740
|
simsimd_avx512_f32_l2sq(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n) {
|
|
739
741
|
__m512 d2_vec = _mm512_set1_ps(0);
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
742
|
+
__m512 a_vec, b_vec;
|
|
743
|
+
|
|
744
|
+
simsimd_avx512_f32_l2sq_cycle:
|
|
745
|
+
if (n < 16) {
|
|
746
|
+
__mmask16 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
747
|
+
a_vec = _mm512_maskz_loadu_ps(mask, a);
|
|
748
|
+
b_vec = _mm512_maskz_loadu_ps(mask, b);
|
|
749
|
+
n = 0;
|
|
750
|
+
} else {
|
|
751
|
+
a_vec = _mm512_loadu_ps(a);
|
|
752
|
+
b_vec = _mm512_loadu_ps(b);
|
|
753
|
+
a += 16, b += 16, n -= 16;
|
|
754
|
+
}
|
|
755
|
+
__m512 d_vec = _mm512_sub_ps(a_vec, b_vec);
|
|
756
|
+
d2_vec = _mm512_fmadd_ps(d_vec, d_vec, d2_vec);
|
|
757
|
+
if (n)
|
|
758
|
+
goto simsimd_avx512_f32_l2sq_cycle;
|
|
759
|
+
|
|
747
760
|
return _mm512_reduce_add_ps(d2_vec);
|
|
748
761
|
}
|
|
749
762
|
|
|
750
|
-
__attribute__((target("avx512f,avx512vl"))) //
|
|
763
|
+
__attribute__((target("avx512f,avx512vl,bmi2"))) //
|
|
751
764
|
inline static simsimd_f32_t
|
|
752
765
|
simsimd_avx512_f32_ip(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n) {
|
|
753
766
|
__m512 ab_vec = _mm512_set1_ps(0);
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
767
|
+
__m512 a_vec, b_vec;
|
|
768
|
+
|
|
769
|
+
simsimd_avx512_f32_ip_cycle:
|
|
770
|
+
if (n < 16) {
|
|
771
|
+
__mmask16 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
772
|
+
a_vec = _mm512_maskz_loadu_ps(mask, a);
|
|
773
|
+
b_vec = _mm512_maskz_loadu_ps(mask, b);
|
|
774
|
+
n = 0;
|
|
775
|
+
} else {
|
|
776
|
+
a_vec = _mm512_loadu_ps(a);
|
|
777
|
+
b_vec = _mm512_loadu_ps(b);
|
|
778
|
+
a += 16, b += 16, n -= 16;
|
|
779
|
+
}
|
|
780
|
+
ab_vec = _mm512_fmadd_ps(a_vec, b_vec, ab_vec);
|
|
781
|
+
if (n)
|
|
782
|
+
goto simsimd_avx512_f32_ip_cycle;
|
|
783
|
+
|
|
760
784
|
return 1 - _mm512_reduce_add_ps(ab_vec);
|
|
761
785
|
}
|
|
762
786
|
|
|
763
|
-
__attribute__((target("avx512f,avx512vl"))) //
|
|
787
|
+
__attribute__((target("avx512f,avx512vl,bmi2"))) //
|
|
764
788
|
inline static simsimd_f32_t
|
|
765
789
|
simsimd_avx512_f32_cos(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n) {
|
|
766
790
|
__m512 ab_vec = _mm512_set1_ps(0);
|
|
767
791
|
__m512 a2_vec = _mm512_set1_ps(0);
|
|
768
792
|
__m512 b2_vec = _mm512_set1_ps(0);
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
793
|
+
__m512 a_vec, b_vec;
|
|
794
|
+
|
|
795
|
+
simsimd_avx512_f32_cos_cycle:
|
|
796
|
+
if (n < 16) {
|
|
797
|
+
__mmask16 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
798
|
+
a_vec = _mm512_maskz_loadu_ps(mask, a);
|
|
799
|
+
b_vec = _mm512_maskz_loadu_ps(mask, b);
|
|
800
|
+
n = 0;
|
|
801
|
+
} else {
|
|
802
|
+
a_vec = _mm512_loadu_ps(a);
|
|
803
|
+
b_vec = _mm512_loadu_ps(b);
|
|
804
|
+
a += 16, b += 16, n -= 16;
|
|
805
|
+
}
|
|
806
|
+
ab_vec = _mm512_fmadd_ps(a_vec, b_vec, ab_vec);
|
|
807
|
+
a2_vec = _mm512_fmadd_ps(a_vec, a_vec, a2_vec);
|
|
808
|
+
b2_vec = _mm512_fmadd_ps(b_vec, b_vec, b2_vec);
|
|
809
|
+
if (n)
|
|
810
|
+
goto simsimd_avx512_f32_cos_cycle;
|
|
778
811
|
|
|
779
812
|
simsimd_f32_t ab = _mm512_reduce_add_ps(ab_vec);
|
|
780
813
|
simsimd_f32_t a2 = _mm512_reduce_add_ps(a2_vec);
|
|
781
814
|
simsimd_f32_t b2 = _mm512_reduce_add_ps(b2_vec);
|
|
782
815
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
return
|
|
816
|
+
// Compute the reciprocal square roots of a2 and b2
|
|
817
|
+
__m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
|
|
818
|
+
simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
|
|
819
|
+
simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
|
|
820
|
+
return 1 - ab * rsqrt_a2 * rsqrt_b2;
|
|
788
821
|
}
|
|
789
822
|
|
|
790
823
|
/*
|
|
@@ -795,61 +828,92 @@ simsimd_avx512_f32_cos(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_s
|
|
|
795
828
|
* - Implements: L2 squared, inner product, cosine similarity.
|
|
796
829
|
* - Uses `_mm512_maskz_loadu_epi16` intrinsics to perform masked unaligned loads.
|
|
797
830
|
* - Uses `f16` for both storage and accumulation, assuming it's resolution is enough for average case.
|
|
798
|
-
* - Requires compiler capabilities: avx512fp16, avx512f, avx512vl.
|
|
831
|
+
* - Requires compiler capabilities: avx512fp16, avx512f, avx512vl, bmi2.
|
|
799
832
|
*/
|
|
800
833
|
|
|
801
|
-
__attribute__((target("avx512fp16,avx512vl,avx512f"))) //
|
|
834
|
+
__attribute__((target("avx512fp16,avx512vl,avx512f,bmi2"))) //
|
|
802
835
|
inline static simsimd_f32_t
|
|
803
836
|
simsimd_avx512_f16_l2sq(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
|
|
804
837
|
__m512h d2_vec = _mm512_set1_ph(0);
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
838
|
+
__m512i a_i16_vec, b_i16_vec;
|
|
839
|
+
|
|
840
|
+
simsimd_avx512_f16_l2sq_cycle:
|
|
841
|
+
if (n < 32) {
|
|
842
|
+
__mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
843
|
+
a_i16_vec = _mm512_maskz_loadu_epi16(mask, a);
|
|
844
|
+
b_i16_vec = _mm512_maskz_loadu_epi16(mask, b);
|
|
845
|
+
n = 0;
|
|
846
|
+
} else {
|
|
847
|
+
a_i16_vec = _mm512_loadu_epi16(a);
|
|
848
|
+
b_i16_vec = _mm512_loadu_epi16(b);
|
|
849
|
+
a += 32, b += 32, n -= 32;
|
|
850
|
+
}
|
|
851
|
+
__m512h d_vec = _mm512_sub_ph(_mm512_castsi512_ph(a_i16_vec), _mm512_castsi512_ph(b_i16_vec));
|
|
852
|
+
d2_vec = _mm512_fmadd_ph(d_vec, d_vec, d2_vec);
|
|
853
|
+
if (n)
|
|
854
|
+
goto simsimd_avx512_f16_l2sq_cycle;
|
|
855
|
+
|
|
812
856
|
return _mm512_reduce_add_ph(d2_vec);
|
|
813
857
|
}
|
|
814
858
|
|
|
815
|
-
__attribute__((target("avx512fp16,avx512vl,avx512f"))) //
|
|
859
|
+
__attribute__((target("avx512fp16,avx512vl,avx512f,bmi2"))) //
|
|
816
860
|
inline static simsimd_f32_t
|
|
817
861
|
simsimd_avx512_f16_ip(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
|
|
818
862
|
__m512h ab_vec = _mm512_set1_ph(0);
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
863
|
+
__m512i a_i16_vec, b_i16_vec;
|
|
864
|
+
|
|
865
|
+
simsimd_avx512_f16_ip_cycle:
|
|
866
|
+
if (n < 32) {
|
|
867
|
+
__mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
868
|
+
a_i16_vec = _mm512_maskz_loadu_epi16(mask, a);
|
|
869
|
+
b_i16_vec = _mm512_maskz_loadu_epi16(mask, b);
|
|
870
|
+
n = 0;
|
|
871
|
+
} else {
|
|
872
|
+
a_i16_vec = _mm512_loadu_epi16(a);
|
|
873
|
+
b_i16_vec = _mm512_loadu_epi16(b);
|
|
874
|
+
a += 32, b += 32, n -= 32;
|
|
875
|
+
}
|
|
876
|
+
ab_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(a_i16_vec), _mm512_castsi512_ph(b_i16_vec), ab_vec);
|
|
877
|
+
if (n)
|
|
878
|
+
goto simsimd_avx512_f16_ip_cycle;
|
|
879
|
+
|
|
825
880
|
return 1 - _mm512_reduce_add_ph(ab_vec);
|
|
826
881
|
}
|
|
827
882
|
|
|
828
|
-
__attribute__((target("avx512fp16,avx512vl,avx512f"))) //
|
|
883
|
+
__attribute__((target("avx512fp16,avx512vl,avx512f,bmi2"))) //
|
|
829
884
|
inline static simsimd_f32_t
|
|
830
885
|
simsimd_avx512_f16_cos(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
|
|
831
886
|
__m512h ab_vec = _mm512_set1_ph(0);
|
|
832
887
|
__m512h a2_vec = _mm512_set1_ph(0);
|
|
833
888
|
__m512h b2_vec = _mm512_set1_ph(0);
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
889
|
+
__m512i a_i16_vec, b_i16_vec;
|
|
890
|
+
|
|
891
|
+
simsimd_avx512_f16_cos_cycle:
|
|
892
|
+
if (n < 32) {
|
|
893
|
+
__mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
894
|
+
a_i16_vec = _mm512_maskz_loadu_epi16(mask, a);
|
|
895
|
+
b_i16_vec = _mm512_maskz_loadu_epi16(mask, b);
|
|
896
|
+
n = 0;
|
|
897
|
+
} else {
|
|
898
|
+
a_i16_vec = _mm512_loadu_epi16(a);
|
|
899
|
+
b_i16_vec = _mm512_loadu_epi16(b);
|
|
900
|
+
a += 32, b += 32, n -= 32;
|
|
901
|
+
}
|
|
902
|
+
ab_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(a_i16_vec), _mm512_castsi512_ph(b_i16_vec), ab_vec);
|
|
903
|
+
a2_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(a_i16_vec), _mm512_castsi512_ph(a_i16_vec), a2_vec);
|
|
904
|
+
b2_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(b_i16_vec), _mm512_castsi512_ph(b_i16_vec), b2_vec);
|
|
905
|
+
if (n)
|
|
906
|
+
goto simsimd_avx512_f16_cos_cycle;
|
|
843
907
|
|
|
844
908
|
simsimd_f32_t ab = _mm512_reduce_add_ph(ab_vec);
|
|
845
909
|
simsimd_f32_t a2 = _mm512_reduce_add_ph(a2_vec);
|
|
846
910
|
simsimd_f32_t b2 = _mm512_reduce_add_ph(b2_vec);
|
|
847
911
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return
|
|
912
|
+
// Compute the reciprocal square roots of a2 and b2
|
|
913
|
+
__m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
|
|
914
|
+
simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
|
|
915
|
+
simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
|
|
916
|
+
return 1 - ab * rsqrt_a2 * rsqrt_b2;
|
|
853
917
|
}
|
|
854
918
|
|
|
855
919
|
/*
|
|
@@ -860,54 +924,72 @@ simsimd_avx512_f16_cos(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_s
|
|
|
860
924
|
* - Implements: L2 squared, cosine similarity, inner product (same as cosine).
|
|
861
925
|
* - Uses `_mm512_maskz_loadu_epi16` intrinsics to perform masked unaligned loads.
|
|
862
926
|
* - Uses `i8` for storage, `i16` for multiplication, and `i32` for accumulation, if no better option is available.
|
|
863
|
-
* -
|
|
927
|
+
* - Uses BMI2 `bzhi` instructions to build masks without branches or conditional moves.
|
|
928
|
+
* - Requires compiler capabilities: avx512f, avx512vl, avx512bw, avx512vnni, bmi2.
|
|
864
929
|
*/
|
|
865
930
|
|
|
866
|
-
__attribute__((target("avx512vl,avx512f,avx512bw"))) //
|
|
931
|
+
__attribute__((target("avx512vl,avx512f,avx512bw,avx512vnni,bmi2"))) //
|
|
867
932
|
inline static simsimd_f32_t
|
|
868
933
|
simsimd_avx512_i8_l2sq(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n) {
|
|
869
934
|
__m512i d2_i32s_vec = _mm512_setzero_si512();
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
935
|
+
__m512i a_vec, b_vec, d_i16s_vec;
|
|
936
|
+
|
|
937
|
+
simsimd_avx512_i8_l2sq_cycle:
|
|
938
|
+
if (n < 32) {
|
|
939
|
+
__mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
940
|
+
a_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, a));
|
|
941
|
+
b_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, b));
|
|
942
|
+
n = 0;
|
|
943
|
+
} else {
|
|
944
|
+
a_vec = _mm512_cvtepi8_epi16(_mm256_loadu_epi8(a));
|
|
945
|
+
b_vec = _mm512_cvtepi8_epi16(_mm256_loadu_epi8(b));
|
|
946
|
+
a += 32, b += 32, n -= 32;
|
|
947
|
+
}
|
|
948
|
+
d_i16s_vec = _mm512_sub_epi16(a_vec, b_vec);
|
|
949
|
+
d2_i32s_vec = _mm512_dpwssd_epi32(d2_i32s_vec, d_i16s_vec, d_i16s_vec);
|
|
950
|
+
if (n)
|
|
951
|
+
goto simsimd_avx512_i8_l2sq_cycle;
|
|
878
952
|
|
|
879
953
|
return _mm512_reduce_add_epi32(d2_i32s_vec);
|
|
880
954
|
}
|
|
881
955
|
|
|
882
|
-
__attribute__((target("avx512vl,avx512f,avx512bw"))) //
|
|
956
|
+
__attribute__((target("avx512vl,avx512f,avx512bw,avx512vnni,bmi2"))) //
|
|
883
957
|
inline static simsimd_f32_t
|
|
884
958
|
simsimd_avx512_i8_cos(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n) {
|
|
885
959
|
__m512i ab_i32s_vec = _mm512_setzero_si512();
|
|
886
960
|
__m512i a2_i32s_vec = _mm512_setzero_si512();
|
|
887
961
|
__m512i b2_i32s_vec = _mm512_setzero_si512();
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
962
|
+
__m512i a_vec, b_vec;
|
|
963
|
+
|
|
964
|
+
simsimd_avx512_i8_cos_cycle:
|
|
965
|
+
if (n < 64) {
|
|
966
|
+
__mmask64 mask = _bzhi_u64(0xFFFFFFFFFFFFFFFF, n);
|
|
967
|
+
a_vec = _mm512_maskz_loadu_epi8(mask, a);
|
|
968
|
+
b_vec = _mm512_maskz_loadu_epi8(mask, b);
|
|
969
|
+
n = 0;
|
|
970
|
+
} else {
|
|
971
|
+
a_vec = _mm512_loadu_epi8(a);
|
|
972
|
+
b_vec = _mm512_loadu_epi8(b);
|
|
973
|
+
a += 64, b += 64, n -= 64;
|
|
974
|
+
}
|
|
975
|
+
ab_i32s_vec = _mm512_dpbusd_epi32(ab_i32s_vec, a_vec, b_vec);
|
|
976
|
+
a2_i32s_vec = _mm512_dpbusd_epi32(a2_i32s_vec, a_vec, a_vec);
|
|
977
|
+
b2_i32s_vec = _mm512_dpbusd_epi32(b2_i32s_vec, b_vec, b_vec);
|
|
978
|
+
if (n)
|
|
979
|
+
goto simsimd_avx512_i8_cos_cycle;
|
|
980
|
+
|
|
981
|
+
simsimd_f32_t ab = _mm512_reduce_add_epi32(ab_i32s_vec);
|
|
982
|
+
simsimd_f32_t a2 = _mm512_reduce_add_epi32(a2_i32s_vec);
|
|
983
|
+
simsimd_f32_t b2 = _mm512_reduce_add_epi32(b2_i32s_vec);
|
|
984
|
+
|
|
985
|
+
// Compute the reciprocal square roots of a2 and b2
|
|
986
|
+
__m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
|
|
987
|
+
simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
|
|
988
|
+
simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
|
|
989
|
+
return 1 - ab * rsqrt_a2 * rsqrt_b2;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
__attribute__((target("avx512vl,avx512f,avx512bw,avx512vnni,bmi2"))) //
|
|
911
993
|
inline static simsimd_f32_t
|
|
912
994
|
simsimd_avx512_i8_ip(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n) {
|
|
913
995
|
return simsimd_avx512_i8_cos(a, b, n);
|
package/simsimd/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simsimd",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Vector Similarity Functions 3x-200x Faster than SciPy and NumPy",
|
|
5
5
|
"author": "Ash Vardanian",
|
|
6
6
|
"license": "Apache 2.0",
|
|
@@ -24,13 +24,9 @@
|
|
|
24
24
|
"bench": "node ./javascript/bench.js"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@semantic-release/exec": "^6.0.3",
|
|
28
|
-
"@semantic-release/git": "^10.0.1",
|
|
29
27
|
"benchmark": "^2.1.4",
|
|
30
|
-
"conventional-changelog-eslint": "^3.0.9",
|
|
31
28
|
"mathjs": "^11.11.2",
|
|
32
29
|
"node-gyp": "^8.3.0",
|
|
33
|
-
"semantic-release": "^21.1.2",
|
|
34
30
|
"typescript": "^5.1.6",
|
|
35
31
|
"usearch": "^2.7.2"
|
|
36
32
|
}
|
package/simsimd/python/lib.c
CHANGED
package/simsimd/python/test.py
CHANGED
|
@@ -3,8 +3,10 @@ import numpy as np
|
|
|
3
3
|
import simsimd as simd
|
|
4
4
|
import scipy.spatial.distance as spd
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# For normalized distances we use the absolute tolerance, because the result is close to zero.
|
|
7
|
+
# For unnormalized ones (like squared Euclidean or Jaccard), we use the relative.
|
|
8
|
+
SIMSIMD_RTOL = 0.2
|
|
9
|
+
SIMSIMD_ATOL = 0.1
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def test_pointers_availability():
|
|
@@ -35,7 +37,7 @@ def test_dot(ndim, dtype):
|
|
|
35
37
|
expected = 1 - np.inner(a, b)
|
|
36
38
|
result = simd.inner(a, b)
|
|
37
39
|
|
|
38
|
-
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=
|
|
40
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=0)
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
@pytest.mark.repeat(50)
|
|
@@ -49,7 +51,7 @@ def test_sqeuclidean(ndim, dtype):
|
|
|
49
51
|
expected = spd.sqeuclidean(a, b)
|
|
50
52
|
result = simd.sqeuclidean(a, b)
|
|
51
53
|
|
|
52
|
-
np.testing.assert_allclose(expected, result, atol=
|
|
54
|
+
np.testing.assert_allclose(expected, result, atol=0, rtol=SIMSIMD_RTOL)
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
@pytest.mark.repeat(50)
|
|
@@ -63,8 +65,31 @@ def test_cosine(ndim, dtype):
|
|
|
63
65
|
expected = spd.cosine(a, b)
|
|
64
66
|
result = simd.cosine(a, b)
|
|
65
67
|
|
|
66
|
-
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=
|
|
68
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=0)
|
|
67
69
|
|
|
70
|
+
@pytest.mark.repeat(50)
|
|
71
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
72
|
+
def test_cosine_i8(ndim):
|
|
73
|
+
"""Compares the simd.cosine() function with scipy.spatial.distance.cosine(), measuring the accuracy error for 8-bit int types."""
|
|
74
|
+
a = np.random.randint(0, 100, size=ndim, dtype=np.int8)
|
|
75
|
+
b = np.random.randint(0, 100, size=ndim, dtype=np.int8)
|
|
76
|
+
|
|
77
|
+
expected = spd.cosine(a.astype(np.float32), b.astype(np.float32))
|
|
78
|
+
result = simd.cosine(a, b)
|
|
79
|
+
|
|
80
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=0)
|
|
81
|
+
|
|
82
|
+
@pytest.mark.repeat(50)
|
|
83
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
84
|
+
def test_sqeuclidean_i8(ndim):
|
|
85
|
+
"""Compares the simd.sqeuclidean() function with scipy.spatial.distance.sqeuclidean(), measuring the accuracy error for 8-bit int types."""
|
|
86
|
+
a = np.random.randint(0, 100, size=ndim, dtype=np.int8)
|
|
87
|
+
b = np.random.randint(0, 100, size=ndim, dtype=np.int8)
|
|
88
|
+
|
|
89
|
+
expected = spd.sqeuclidean(a.astype(np.float32), b.astype(np.float32))
|
|
90
|
+
result = simd.sqeuclidean(a, b)
|
|
91
|
+
|
|
92
|
+
np.testing.assert_allclose(expected, result, atol=0, rtol=SIMSIMD_RTOL)
|
|
68
93
|
|
|
69
94
|
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
70
95
|
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
@@ -87,26 +112,26 @@ def test_cosine_zero_vector(ndim, dtype):
|
|
|
87
112
|
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
88
113
|
def test_hamming(ndim):
|
|
89
114
|
"""Compares the simd.hamming() function with scipy.spatial.distance.hamming."""
|
|
90
|
-
a = np.
|
|
91
|
-
b = np.
|
|
115
|
+
a = np.random.randint(2, size=ndim).astype(np.uint8)
|
|
116
|
+
b = np.random.randint(2, size=ndim).astype(np.uint8)
|
|
92
117
|
|
|
93
|
-
expected = spd.hamming(a, b)
|
|
94
|
-
result = simd.hamming(a, b)
|
|
118
|
+
expected = spd.hamming(a, b) * ndim
|
|
119
|
+
result = simd.hamming(np.packbits(a), np.packbits(b))
|
|
95
120
|
|
|
96
|
-
np.testing.assert_allclose(expected, result, atol=
|
|
121
|
+
np.testing.assert_allclose(expected, result, atol=0, rtol=SIMSIMD_RTOL)
|
|
97
122
|
|
|
98
123
|
|
|
99
124
|
@pytest.mark.repeat(50)
|
|
100
125
|
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
101
126
|
def test_jaccard(ndim):
|
|
102
127
|
"""Compares the simd.jaccard() function with scipy.spatial.distance.jaccard."""
|
|
103
|
-
a = np.
|
|
104
|
-
b = np.
|
|
128
|
+
a = np.random.randint(2, size=ndim).astype(np.uint8)
|
|
129
|
+
b = np.random.randint(2, size=ndim).astype(np.uint8)
|
|
105
130
|
|
|
106
|
-
expected = spd.jaccard(a, b)
|
|
107
|
-
result = simd.jaccard(a, b)
|
|
131
|
+
expected = spd.jaccard(a, b)
|
|
132
|
+
result = simd.jaccard(np.packbits(a), np.packbits(b))
|
|
108
133
|
|
|
109
|
-
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=
|
|
134
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=0)
|
|
110
135
|
|
|
111
136
|
|
|
112
137
|
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
@@ -119,39 +144,39 @@ def test_batch(ndim, dtype):
|
|
|
119
144
|
B = np.random.randn(10, ndim).astype(dtype)
|
|
120
145
|
result_np = [spd.sqeuclidean(A[i], B[i]) for i in range(10)]
|
|
121
146
|
result_simd = simd.sqeuclidean(A, B)
|
|
122
|
-
assert np.allclose(result_simd, result_np, atol=
|
|
147
|
+
assert np.allclose(result_simd, result_np, atol=0, rtol=SIMSIMD_RTOL)
|
|
123
148
|
|
|
124
149
|
# Distance between matrixes A (N x D scalars) and B (1 x D scalars) is an array with N floats.
|
|
125
150
|
B = np.random.randn(1, ndim).astype(dtype)
|
|
126
151
|
result_np = [spd.sqeuclidean(A[i], B[0]) for i in range(10)]
|
|
127
152
|
result_simd = simd.sqeuclidean(A, B)
|
|
128
|
-
assert np.allclose(result_simd, result_np, atol=
|
|
153
|
+
assert np.allclose(result_simd, result_np, atol=0, rtol=SIMSIMD_RTOL)
|
|
129
154
|
|
|
130
155
|
# Distance between matrixes A (1 x D scalars) and B (N x D scalars) is an array with N floats.
|
|
131
156
|
A = np.random.randn(1, ndim).astype(dtype)
|
|
132
157
|
B = np.random.randn(10, ndim).astype(dtype)
|
|
133
158
|
result_np = [spd.sqeuclidean(A[0], B[i]) for i in range(10)]
|
|
134
159
|
result_simd = simd.sqeuclidean(A, B)
|
|
135
|
-
assert np.allclose(result_simd, result_np, atol=
|
|
160
|
+
assert np.allclose(result_simd, result_np, atol=0, rtol=SIMSIMD_RTOL)
|
|
136
161
|
|
|
137
162
|
# Distance between matrix A (N x D scalars) and array B (D scalars) is an array with N floats.
|
|
138
163
|
A = np.random.randn(10, ndim).astype(dtype)
|
|
139
164
|
B = np.random.randn(ndim).astype(dtype)
|
|
140
165
|
result_np = [spd.sqeuclidean(A[i], B) for i in range(10)]
|
|
141
166
|
result_simd = simd.sqeuclidean(A, B)
|
|
142
|
-
assert np.allclose(result_simd, result_np, atol=
|
|
167
|
+
assert np.allclose(result_simd, result_np, atol=0, rtol=SIMSIMD_RTOL)
|
|
143
168
|
|
|
144
169
|
# Distance between matrix B (N x D scalars) and array A (D scalars) is an array with N floats.
|
|
145
170
|
B = np.random.randn(10, ndim).astype(dtype)
|
|
146
171
|
A = np.random.randn(ndim).astype(dtype)
|
|
147
172
|
result_np = [spd.sqeuclidean(B[i], A) for i in range(10)]
|
|
148
173
|
result_simd = simd.sqeuclidean(B, A)
|
|
149
|
-
assert np.allclose(result_simd, result_np, atol=
|
|
174
|
+
assert np.allclose(result_simd, result_np, atol=0, rtol=SIMSIMD_RTOL)
|
|
150
175
|
|
|
151
176
|
|
|
152
177
|
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
153
178
|
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
154
|
-
@pytest.mark.parametrize("metric", ["
|
|
179
|
+
@pytest.mark.parametrize("metric", ["cosine"])
|
|
155
180
|
def test_cdist(ndim, dtype, metric):
|
|
156
181
|
"""Compares the simd.cdist() function with scipy.spatial.distance.cdist(), measuring the accuracy error for f16, and f32 types using sqeuclidean and cosine metrics."""
|
|
157
182
|
|
|
@@ -167,4 +192,4 @@ def test_cdist(ndim, dtype, metric):
|
|
|
167
192
|
result = simd.cdist(A, B, metric=metric)
|
|
168
193
|
|
|
169
194
|
# Assert they're close.
|
|
170
|
-
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=
|
|
195
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=0)
|