usearch 2.8.0 → 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.
@@ -370,58 +370,70 @@ simsimd_avx512_f32_log2(__m512 x) {
370
370
  return _mm512_add_ps(_mm512_mul_ps(p, _mm512_sub_ps(m, one)), e);
371
371
  }
372
372
 
373
- __attribute__((target("avx512f,avx512vl"))) //
373
+ __attribute__((target("avx512f,avx512vl,bmi2"))) //
374
374
  inline static simsimd_f32_t
375
375
  simsimd_avx512_f32_kl(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n) {
376
376
  __m512 sum_vec = _mm512_set1_ps(0);
377
377
  simsimd_f32_t epsilon = 1e-6;
378
378
  __m512 epsilon_vec = _mm512_set1_ps(epsilon);
379
- for (simsimd_size_t i = 0; i < n; i += 16) {
380
- __mmask16 mask = n - i >= 16 ? 0xFFFF : ((1u << (n - i)) - 1u);
381
- __m512 a_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, a + i), epsilon_vec);
382
- __m512 b_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, b + i), epsilon_vec);
383
- __m512 ratio_vec = _mm512_div_ps(a_vec, b_vec);
384
- __m512 log_ratio_vec = simsimd_avx512_f32_log2(ratio_vec);
385
- __m512 prod_vec = _mm512_mul_ps(a_vec, log_ratio_vec);
386
- sum_vec = _mm512_maskz_add_ps(mask, sum_vec, prod_vec);
379
+ __m512 a_vec, b_vec;
380
+
381
+ simsimd_avx512_f32_kl_cycle:
382
+ if (n < 16) {
383
+ __mmask16 mask = _bzhi_u32(0xFFFFFFFF, n);
384
+ a_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, a), epsilon_vec);
385
+ b_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, b), epsilon_vec);
386
+ n = 0;
387
+ } else {
388
+ a_vec = _mm512_add_ps(_mm512_loadu_ps(a), epsilon_vec);
389
+ b_vec = _mm512_add_ps(_mm512_loadu_ps(b), epsilon_vec);
390
+ a += 16, b += 16, n -= 16;
387
391
  }
392
+ __m512 ratio_vec = _mm512_div_ps(a_vec, b_vec);
393
+ __m512 log_ratio_vec = simsimd_avx512_f32_log2(ratio_vec);
394
+ __m512 prod_vec = _mm512_mul_ps(a_vec, log_ratio_vec);
395
+ sum_vec = _mm512_add_ps(sum_vec, prod_vec);
396
+ if (n)
397
+ goto simsimd_avx512_f32_kl_cycle;
398
+
388
399
  simsimd_f32_t log2_normalizer = 0.693147181f;
389
400
  return _mm512_reduce_add_ps(sum_vec) * log2_normalizer;
390
401
  }
391
402
 
392
- __attribute__((target("avx512f,avx512vl"))) //
403
+ __attribute__((target("avx512f,avx512vl,bmi2"))) //
393
404
  inline static simsimd_f32_t
394
405
  simsimd_avx512_f32_js(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n) {
395
406
  __m512 sum_a_vec = _mm512_set1_ps(0);
396
407
  __m512 sum_b_vec = _mm512_set1_ps(0);
397
408
  simsimd_f32_t epsilon = 1e-6;
398
409
  __m512 epsilon_vec = _mm512_set1_ps(epsilon);
399
- for (simsimd_size_t i = 0; i < n; i += 16) {
400
- __mmask16 mask = n - i >= 16 ? 0xFFFF : ((1u << (n - i)) - 1u);
401
- __m512 a_vec = _mm512_maskz_loadu_ps(mask, a + i);
402
- __m512 b_vec = _mm512_maskz_loadu_ps(mask, b + i);
403
- __m512 m_vec = _mm512_mul_ps(_mm512_add_ps(a_vec, b_vec), _mm512_set1_ps(0.5f)); // M = (P + Q) / 2
404
-
405
- // Avoid division by zero problems from probabilities under zero down the road.
406
- // Masking is a nicer way to do this, than adding the `epsilon` to every component.
407
- __mmask16 nonzero_mask_a = _mm512_cmp_ps_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
408
- __mmask16 nonzero_mask_b = _mm512_cmp_ps_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
409
- __mmask16 nonzero_mask = nonzero_mask_a & nonzero_mask_b & mask;
410
-
411
- // Division is an expensive operation. Instead of doing it twice,
412
- // we can approximate the reciprocal of `m` and multiply instead.
413
- __m512 m_recip_approx = _mm512_rcp14_ps(m_vec); // or `_mm512_rcp28_ps` for slightly better precision
414
- __m512 ratio_a_vec = _mm512_mul_ps(a_vec, m_recip_approx);
415
- __m512 ratio_b_vec = _mm512_mul_ps(b_vec, m_recip_approx);
416
-
417
- // The natural logarithm is equivalent to `log2`, multiplied by the `loge(2)`
418
- __m512 log_ratio_a_vec = simsimd_avx512_f32_log2(ratio_a_vec);
419
- __m512 log_ratio_b_vec = simsimd_avx512_f32_log2(ratio_b_vec);
420
-
421
- // Instead of separate multiplication and addition, invoke the FMA
422
- sum_a_vec = _mm512_maskz_fmadd_ps(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
423
- sum_b_vec = _mm512_maskz_fmadd_ps(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
410
+ __m512 a_vec, b_vec;
411
+
412
+ simsimd_avx512_f32_js_cycle:
413
+ if (n < 16) {
414
+ __mmask16 mask = _bzhi_u32(0xFFFFFFFF, n);
415
+ a_vec = _mm512_maskz_loadu_ps(mask, a);
416
+ b_vec = _mm512_maskz_loadu_ps(mask, b);
417
+ n = 0;
418
+ } else {
419
+ a_vec = _mm512_loadu_ps(a);
420
+ b_vec = _mm512_loadu_ps(b);
421
+ a += 16, b += 16, n -= 16;
424
422
  }
423
+ __m512 m_vec = _mm512_mul_ps(_mm512_add_ps(a_vec, b_vec), _mm512_set1_ps(0.5f));
424
+ __mmask16 nonzero_mask_a = _mm512_cmp_ps_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
425
+ __mmask16 nonzero_mask_b = _mm512_cmp_ps_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
426
+ __mmask16 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
427
+ __m512 m_recip_approx = _mm512_rcp14_ps(m_vec);
428
+ __m512 ratio_a_vec = _mm512_mul_ps(a_vec, m_recip_approx);
429
+ __m512 ratio_b_vec = _mm512_mul_ps(b_vec, m_recip_approx);
430
+ __m512 log_ratio_a_vec = simsimd_avx512_f32_log2(ratio_a_vec);
431
+ __m512 log_ratio_b_vec = simsimd_avx512_f32_log2(ratio_b_vec);
432
+ sum_a_vec = _mm512_maskz_fmadd_ps(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
433
+ sum_b_vec = _mm512_maskz_fmadd_ps(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
434
+ if (n)
435
+ goto simsimd_avx512_f32_js_cycle;
436
+
425
437
  simsimd_f32_t log2_normalizer = 0.693147181f;
426
438
  return _mm512_reduce_add_ps(_mm512_add_ps(sum_a_vec, sum_b_vec)) * 0.5f * log2_normalizer;
427
439
  }
@@ -456,56 +468,68 @@ simsimd_avx512_f16_log2(__m512h x) {
456
468
  return _mm512_add_ph(_mm512_mul_ph(p, _mm512_sub_ph(m, one)), e);
457
469
  }
458
470
 
459
- __attribute__((target("avx512f,avx512vl,avx512fp16"))) //
471
+ __attribute__((target("avx512f,avx512vl,avx512fp16,bmi2"))) //
460
472
  inline static simsimd_f32_t
461
473
  simsimd_avx512_f16_kl(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
462
474
  __m512h sum_vec = _mm512_set1_ph((_Float16)0);
463
475
  __m512h epsilon_vec = _mm512_set1_ph((_Float16)1e-6f);
464
- for (simsimd_size_t i = 0; i < n; i += 32) {
465
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
466
- __m512h a_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a + i)), epsilon_vec);
467
- __m512h b_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b + i)), epsilon_vec);
468
- __m512h ratio_vec = _mm512_div_ph(a_vec, b_vec);
469
- __m512h log_ratio_vec = simsimd_avx512_f16_log2(ratio_vec);
470
- __m512h prod_vec = _mm512_mul_ph(a_vec, log_ratio_vec);
471
- sum_vec = _mm512_maskz_add_ph(mask, sum_vec, prod_vec);
476
+ __m512h a_vec, b_vec;
477
+
478
+ simsimd_avx512_f16_kl_cycle:
479
+ if (n < 32) {
480
+ __mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
481
+ a_vec = _mm512_maskz_add_ph(mask, _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a)), epsilon_vec);
482
+ b_vec = _mm512_maskz_add_ph(mask, _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b)), epsilon_vec);
483
+ n = 0;
484
+ } else {
485
+ a_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_loadu_epi16(a)), epsilon_vec);
486
+ b_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_loadu_epi16(b)), epsilon_vec);
487
+ a += 32, b += 32, n -= 32;
472
488
  }
489
+ __m512h ratio_vec = _mm512_div_ph(a_vec, b_vec);
490
+ __m512h log_ratio_vec = simsimd_avx512_f16_log2(ratio_vec);
491
+ __m512h prod_vec = _mm512_mul_ph(a_vec, log_ratio_vec);
492
+ sum_vec = _mm512_add_ph(sum_vec, prod_vec);
493
+ if (n)
494
+ goto simsimd_avx512_f16_kl_cycle;
495
+
473
496
  simsimd_f32_t log2_normalizer = 0.693147181f;
474
497
  return _mm512_reduce_add_ph(sum_vec) * log2_normalizer;
475
498
  }
476
499
 
477
- __attribute__((target("avx512f,avx512vl,avx512fp16"))) //
500
+ __attribute__((target("avx512f,avx512vl,avx512fp16,bmi2"))) //
478
501
  inline static simsimd_f32_t
479
502
  simsimd_avx512_f16_js(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
480
503
  __m512h sum_a_vec = _mm512_set1_ph((_Float16)0);
481
504
  __m512h sum_b_vec = _mm512_set1_ph((_Float16)0);
482
505
  __m512h epsilon_vec = _mm512_set1_ph((_Float16)1e-6f);
483
- for (simsimd_size_t i = 0; i < n; i += 32) {
484
- __mmask32 mask = n - i >= 32 ? 0xFFFFFFFF : ((1u << (n - i)) - 1u);
485
- __m512h a_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a + i));
486
- __m512h b_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b + i));
487
- __m512h m_vec = _mm512_mul_ph(_mm512_add_ph(a_vec, b_vec), _mm512_set1_ph((_Float16)0.5f)); // M = (P + Q) / 2
488
-
489
- // Avoid division by zero problems from probabilities under zero down the road.
490
- // Masking is a nicer way to do this, than adding the `epsilon` to every component.
491
- __mmask32 nonzero_mask_a = _mm512_cmp_ph_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
492
- __mmask32 nonzero_mask_b = _mm512_cmp_ph_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
493
- __mmask32 nonzero_mask = nonzero_mask_a & nonzero_mask_b & mask;
494
-
495
- // Division is an expensive operation. Instead of doing it twice,
496
- // we can approximate the reciprocal of `m` and multiply instead.
497
- __m512h m_recip_approx = _mm512_rcp_ph(m_vec);
498
- __m512h ratio_a_vec = _mm512_mul_ph(a_vec, m_recip_approx);
499
- __m512h ratio_b_vec = _mm512_mul_ph(b_vec, m_recip_approx);
500
-
501
- // The natural logarithm is equivalent to `log2`, multiplied by the `loge(2)`
502
- __m512h log_ratio_a_vec = simsimd_avx512_f16_log2(ratio_a_vec);
503
- __m512h log_ratio_b_vec = simsimd_avx512_f16_log2(ratio_b_vec);
504
-
505
- // Instead of separate multiplication and addition, invoke the FMA
506
- sum_a_vec = _mm512_maskz_fmadd_ph(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
507
- sum_b_vec = _mm512_maskz_fmadd_ph(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
506
+ __m512h a_vec, b_vec;
507
+
508
+ simsimd_avx512_f16_js_cycle:
509
+ if (n < 32) {
510
+ __mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
511
+ a_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a));
512
+ b_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b));
513
+ n = 0;
514
+ } else {
515
+ a_vec = _mm512_castsi512_ph(_mm512_loadu_epi16(a));
516
+ b_vec = _mm512_castsi512_ph(_mm512_loadu_epi16(b));
517
+ a += 32, b += 32, n -= 32;
508
518
  }
519
+ __m512h m_vec = _mm512_mul_ph(_mm512_add_ph(a_vec, b_vec), _mm512_set1_ph((_Float16)0.5f));
520
+ __mmask32 nonzero_mask_a = _mm512_cmp_ph_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
521
+ __mmask32 nonzero_mask_b = _mm512_cmp_ph_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
522
+ __mmask32 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
523
+ __m512h m_recip_approx = _mm512_rcp_ph(m_vec);
524
+ __m512h ratio_a_vec = _mm512_mul_ph(a_vec, m_recip_approx);
525
+ __m512h ratio_b_vec = _mm512_mul_ph(b_vec, m_recip_approx);
526
+ __m512h log_ratio_a_vec = simsimd_avx512_f16_log2(ratio_a_vec);
527
+ __m512h log_ratio_b_vec = simsimd_avx512_f16_log2(ratio_b_vec);
528
+ sum_a_vec = _mm512_maskz_fmadd_ph(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
529
+ sum_b_vec = _mm512_maskz_fmadd_ph(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
530
+ if (n)
531
+ goto simsimd_avx512_f16_js_cycle;
532
+
509
533
  simsimd_f32_t log2_normalizer = 0.693147181f;
510
534
  return _mm512_reduce_add_ph(_mm512_add_ph(sum_a_vec, sum_b_vec)) * 0.5f * log2_normalizer;
511
535
  }
@@ -74,6 +74,7 @@ typedef enum {
74
74
  simsimd_cap_x86_avx2fp16_k = 1 << 22, ///< x86 AVX2 with FP16 capability
75
75
  simsimd_cap_x86_avx512fp16_k = 1 << 23, ///< x86 AVX512 with FP16 capability
76
76
  simsimd_cap_x86_avx512vpopcntdq_k = 1 << 24, ///< x86 AVX512 VPOPCNTDQ instruction capability
77
+ simsimd_cap_x86_avx512vnni_k = 1 << 25, ///< x86 AVX512 VNNI instruction capability
77
78
 
78
79
  } simsimd_capability_t;
79
80
 
@@ -112,42 +113,50 @@ inline static simsimd_capability_t simsimd_capabilities() {
112
113
  /// The states of 4 registers populated for a specific "cpuid" assmebly call
113
114
  union four_registers_t {
114
115
  int array[4];
115
- struct {
116
+ struct separate_t {
116
117
  unsigned eax, ebx, ecx, edx;
117
- };
118
+ } named;
118
119
  } info1, info7;
119
120
 
120
121
  #ifdef _MSC_VER
121
122
  __cpuidex(info1.array, 1, 0);
122
123
  __cpuidex(info7.array, 7, 0);
123
124
  #else
124
- __asm__ __volatile__("cpuid" : "=a"(info1.eax), "=b"(info1.ebx), "=c"(info1.ecx), "=d"(info1.edx) : "a"(1), "c"(0));
125
- __asm__ __volatile__("cpuid" : "=a"(info7.eax), "=b"(info7.ebx), "=c"(info7.ecx), "=d"(info7.edx) : "a"(7), "c"(0));
125
+ __asm__ __volatile__("cpuid"
126
+ : "=a"(info1.named.eax), "=b"(info1.named.ebx), "=c"(info1.named.ecx), "=d"(info1.named.edx)
127
+ : "a"(1), "c"(0));
128
+ __asm__ __volatile__("cpuid"
129
+ : "=a"(info7.named.eax), "=b"(info7.named.ebx), "=c"(info7.named.ecx), "=d"(info7.named.edx)
130
+ : "a"(7), "c"(0));
126
131
  #endif
127
132
 
128
133
  // Check for AVX2 (Function ID 7, EBX register)
129
134
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L148
130
- unsigned supports_avx2 = (info7.ebx & 0x00000020) != 0;
135
+ unsigned supports_avx2 = (info7.named.ebx & 0x00000020) != 0;
131
136
  // Check for F16C (Function ID 1, ECX register)
132
137
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L107
133
- unsigned supports_f16c = (info1.ecx & 0x20000000) != 0;
138
+ unsigned supports_f16c = (info1.named.ecx & 0x20000000) != 0;
134
139
  // Check for AVX512F (Function ID 7, EBX register)
135
140
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L155
136
- unsigned supports_avx512f = (info7.ebx & 0x00010000) != 0;
141
+ unsigned supports_avx512f = (info7.named.ebx & 0x00010000) != 0;
137
142
  // Check for AVX512FP16 (Function ID 7, EDX register)
138
143
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L198C9-L198C23
139
- unsigned supports_avx512fp16 = (info7.edx & 0x00800000) != 0;
144
+ unsigned supports_avx512fp16 = (info7.named.edx & 0x00800000) != 0;
140
145
  // Check for VPOPCNTDQ (Function ID 1, ECX register)
141
146
  // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L182C30-L182C40
142
- unsigned supports_avx512vpopcntdq = (info1.ecx & 0x00004000) != 0;
143
-
144
- return (simsimd_capability_t)( //
145
- (simsimd_cap_x86_avx2_k * supports_avx2) | //
146
- (simsimd_cap_x86_avx512_k * supports_avx512f) | //
147
- (simsimd_cap_x86_avx2fp16_k * (supports_avx2 && supports_f16c)) | //
148
- (simsimd_cap_x86_avx512fp16_k * (supports_avx512fp16 && supports_avx512f)) | //
149
- (simsimd_cap_x86_avx512vpopcntdq_k * (supports_avx512vpopcntdq && supports_avx512vpopcntdq)) //
150
- | (simsimd_cap_serial_k));
147
+ unsigned supports_avx512vpopcntdq = (info1.named.ecx & 0x00004000) != 0;
148
+ // Check for VNNI (Function ID 1, ECX register)
149
+ // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L180
150
+ unsigned supports_avx512vnni = (info1.named.ecx & 0x00000800) != 0;
151
+
152
+ return (simsimd_capability_t)( //
153
+ (simsimd_cap_x86_avx2_k * supports_avx2) | //
154
+ (simsimd_cap_x86_avx512_k * supports_avx512f) | //
155
+ (simsimd_cap_x86_avx2fp16_k * (supports_avx2 && supports_f16c)) | //
156
+ (simsimd_cap_x86_avx512fp16_k * (supports_avx512fp16 && supports_avx512f)) | //
157
+ (simsimd_cap_x86_avx512vpopcntdq_k * (supports_avx512vpopcntdq)) | //
158
+ (simsimd_cap_x86_avx512vnni_k * (supports_avx512vnni)) | //
159
+ (simsimd_cap_serial_k));
151
160
 
152
161
  #endif // SIMSIMD_TARGET_X86
153
162
 
@@ -319,11 +328,11 @@ inline static void simsimd_find_metric_punned( //
319
328
  }
320
329
  #endif
321
330
  #if SIMSIMD_TARGET_X86_AVX512
322
- if (viable & simsimd_cap_x86_avx512_k)
331
+ if (viable & simsimd_cap_x86_avx512vnni_k)
323
332
  switch (kind) {
324
- case simsimd_metric_ip_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_ip, *c = simsimd_cap_x86_avx512_k; return;
325
- case simsimd_metric_cos_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_cos, *c = simsimd_cap_x86_avx512_k; return;
326
- case simsimd_metric_l2sq_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_l2sq, *c = simsimd_cap_x86_avx512_k; return;
333
+ case simsimd_metric_ip_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_ip, *c = simsimd_cap_x86_avx512vnni_k; return;
334
+ case simsimd_metric_cos_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_cos, *c = simsimd_cap_x86_avx512vnni_k; return;
335
+ case simsimd_metric_l2sq_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_i8_l2sq, *c = simsimd_cap_x86_avx512vnni_k; return;
327
336
  default: break;
328
337
  }
329
338
  #endif