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.
@@ -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 a2_high_vec = _mm256_setzero_si256();
654
+ __m256i ab_high_vec = _mm256_setzero_si256();
656
655
  __m256i a2_low_vec = _mm256_setzero_si256();
657
- __m256i b2_high_vec = _mm256_setzero_si256();
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 int32
666
- __m256i a_low = _mm256_cvtepi8_epi32(_mm256_castsi256_si128(a_vec));
667
- __m256i a_high = _mm256_cvtepi8_epi32(_mm256_extracti128_si256(a_vec, 1));
668
- __m256i b_low = _mm256_cvtepi8_epi32(_mm256_castsi256_si128(b_vec));
669
- __m256i b_high = _mm256_cvtepi8_epi32(_mm256_extracti128_si256(b_vec, 1));
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, _mm256_mullo_epi32(a_low, b_low));
673
- ab_high_vec = _mm256_add_epi32(ab_high_vec, _mm256_mullo_epi32(a_high, b_high));
674
- a2_low_vec = _mm256_add_epi32(a2_low_vec, _mm256_mullo_epi32(a_low, a_low));
675
- a2_high_vec = _mm256_add_epi32(a2_high_vec, _mm256_mullo_epi32(a_high, a_high));
676
- b2_low_vec = _mm256_add_epi32(b2_low_vec, _mm256_mullo_epi32(b_low, b_low));
677
- b2_high_vec = _mm256_add_epi32(b2_high_vec, _mm256_mullo_epi32(b_high, b_high));
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
- // Replace simsimd_approximate_inverse_square_root with `rsqrtss`
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
- __m128 result = _mm_mul_ss(a2_sqrt_recip, b2_sqrt_recip); // Multiply the reciprocal square roots
711
- result = _mm_mul_ss(result, _mm_set_ss((float)ab)); // Multiply by ab
712
- result = _mm_sub_ss(_mm_set_ss(1.0f), result); // Subtract from 1
713
- return ab != 0 ? _mm_cvtss_f32(result) : 1; // Extract the final result
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
- for (simsimd_size_t i = 0; i < n; i += 16) {
741
- __mmask16 mask = n - i >= 16 ? 0xFFFF : ((1u << (n - i)) - 1u);
742
- __m512 a_vec = _mm512_maskz_loadu_ps(mask, a + i);
743
- __m512 b_vec = _mm512_maskz_loadu_ps(mask, b + i);
744
- __m512 d_vec = _mm512_sub_ps(a_vec, b_vec);
745
- d2_vec = _mm512_fmadd_ps(d_vec, d_vec, d2_vec);
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
- for (simsimd_size_t i = 0; i < n; i += 16) {
755
- __mmask16 mask = n - i >= 16 ? 0xFFFF : ((1u << (n - i)) - 1u);
756
- __m512 a_vec = _mm512_maskz_loadu_ps(mask, a + i);
757
- __m512 b_vec = _mm512_maskz_loadu_ps(mask, b + i);
758
- ab_vec = _mm512_fmadd_ps(a_vec, b_vec, ab_vec);
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
- for (simsimd_size_t i = 0; i < n; i += 16) {
771
- __mmask16 mask = n - i >= 16 ? 0xFFFF : ((1u << (n - i)) - 1u);
772
- __m512 a_vec = _mm512_maskz_loadu_ps(mask, a + i);
773
- __m512 b_vec = _mm512_maskz_loadu_ps(mask, b + i);
774
- ab_vec = _mm512_fmadd_ps(a_vec, b_vec, ab_vec);
775
- a2_vec = _mm512_fmadd_ps(a_vec, a_vec, a2_vec);
776
- b2_vec = _mm512_fmadd_ps(b_vec, b_vec, b2_vec);
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
- __m128d a2_b2 = _mm_set_pd((double)a2, (double)b2);
784
- __m128d rsqrts = _mm_mask_rsqrt14_pd(_mm_setzero_pd(), 0xFF, a2_b2);
785
- double rsqrts_array[2];
786
- _mm_storeu_pd(rsqrts_array, rsqrts);
787
- return ab != 0 ? 1 - ab * rsqrts_array[0] * rsqrts_array[1] : 1;
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
- for (simsimd_size_t i = 0; i < n; i += 32) {
806
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
807
- __m512i a_vec = _mm512_maskz_loadu_epi16(mask, a + i);
808
- __m512i b_vec = _mm512_maskz_loadu_epi16(mask, b + i);
809
- __m512h d_vec = _mm512_sub_ph(_mm512_castsi512_ph(a_vec), _mm512_castsi512_ph(b_vec));
810
- d2_vec = _mm512_fmadd_ph(d_vec, d_vec, d2_vec);
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
- for (simsimd_size_t i = 0; i < n; i += 32) {
820
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
821
- __m512i a_vec = _mm512_maskz_loadu_epi16(mask, a + i);
822
- __m512i b_vec = _mm512_maskz_loadu_epi16(mask, b + i);
823
- ab_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(a_vec), _mm512_castsi512_ph(b_vec), ab_vec);
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
- for (simsimd_size_t i = 0; i < n; i += 32) {
836
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
837
- __m512i a_vec = _mm512_maskz_loadu_epi16(mask, a + i);
838
- __m512i b_vec = _mm512_maskz_loadu_epi16(mask, b + i);
839
- ab_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(a_vec), _mm512_castsi512_ph(b_vec), ab_vec);
840
- a2_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(a_vec), _mm512_castsi512_ph(a_vec), a2_vec);
841
- b2_vec = _mm512_fmadd_ph(_mm512_castsi512_ph(b_vec), _mm512_castsi512_ph(b_vec), b2_vec);
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
- __m128d a2_b2 = _mm_set_pd((double)a2, (double)b2);
849
- __m128d rsqrts = _mm_mask_rsqrt14_pd(_mm_setzero_pd(), 0xFF, a2_b2);
850
- double rsqrts_array[2];
851
- _mm_storeu_pd(rsqrts_array, rsqrts);
852
- return ab != 0 ? 1 - ab * rsqrts_array[0] * rsqrts_array[1] : 1;
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
- * - Requires compiler capabilities: avx512f, avx512vl, avx512bw.
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
- for (simsimd_size_t i = 0; i < n; i += 32) {
872
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
873
- __m512i a_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, a + i)); // Load 8-bit integers
874
- __m512i b_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, b + i)); // Load 8-bit integers
875
- __m512i d_i16s_vec = _mm512_sub_epi16(a_vec, b_vec);
876
- d2_i32s_vec = _mm512_add_epi32(d2_i32s_vec, _mm512_madd_epi16(d_i16s_vec, d_i16s_vec));
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
- for (simsimd_size_t i = 0; i < n; i += 32) {
890
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
891
- __m512i a_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, a + i)); // Load 8-bit integers
892
- __m512i b_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, b + i)); // Load 8-bit integers
893
-
894
- ab_i32s_vec = _mm512_add_epi32(ab_i32s_vec, _mm512_madd_epi16(a_vec, b_vec));
895
- a2_i32s_vec = _mm512_add_epi32(a2_i32s_vec, _mm512_madd_epi16(a_vec, a_vec));
896
- b2_i32s_vec = _mm512_add_epi32(b2_i32s_vec, _mm512_madd_epi16(b_vec, b_vec));
897
- }
898
-
899
- int ab = _mm512_reduce_add_epi32(ab_i32s_vec);
900
- int a2 = _mm512_reduce_add_epi32(a2_i32s_vec);
901
- int b2 = _mm512_reduce_add_epi32(b2_i32s_vec);
902
-
903
- __m128d a2_b2 = _mm_set_pd((double)a2, (double)b2);
904
- __m128d rsqrts = _mm_mask_rsqrt14_pd(_mm_setzero_pd(), 0xFF, a2_b2);
905
- double rsqrts_array[2];
906
- _mm_storeu_pd(rsqrts_array, rsqrts);
907
- return ab != 0 ? 1 - ab * rsqrts_array[0] * rsqrts_array[1] : 1;
908
- }
909
-
910
- __attribute__((target("avx512vl,avx512f,avx512bw"))) //
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);
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "simsimd-ci",
3
+ "version": "1.0.0",
4
+ "devDependencies": {
5
+ "@semantic-release/exec": "^6.0.3",
6
+ "@semantic-release/git": "^10.0.1",
7
+ "conventional-changelog-eslint": "^3.0.9",
8
+ "semantic-release": "^21.1.2"
9
+ }
10
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simsimd",
3
- "version": "3.2.0",
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
  }
@@ -114,6 +114,7 @@ static PyObject* api_get_capabilities(PyObject* self) {
114
114
  ADD_CAP(x86_avx2fp16);
115
115
  ADD_CAP(x86_avx512fp16);
116
116
  ADD_CAP(x86_avx512vpopcntdq);
117
+ ADD_CAP(x86_avx512vnni);
117
118
 
118
119
  #undef ADD_CAP
119
120
 
@@ -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
- SIMSIMD_RTOL = 2
7
- SIMSIMD_ATOL = 3e-1
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=SIMSIMD_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=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
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=SIMSIMD_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.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
91
- b = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
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=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
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.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
104
- b = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
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=SIMSIMD_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=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
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=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
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=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
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=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
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=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
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", ["sqeuclidean", "cosine"])
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=SIMSIMD_RTOL)
195
+ np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=0)
package/wasmer.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name="unum/usearch"
3
- version="2.8.1"
3
+ version="2.8.2"
4
4
  description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
5
  license="Apache-2.0"
6
6
  readme="README.md"