Skip to content

Commit b1d61c3

Browse files
lyang24meta-codesync[bot]
authored andcommitted
Fuse RaBitQ AND-dot and popcount scan (#5412)
Summary: Fuse RaBitQ’s AND-dot and doc-side popcount into a single SIMD pass, avoiding a second scan of the same binary code. Pull Request resolved: #5412 Reviewed By: mnorris11 Differential Revision: D112829641 Pulled By: alibeklfc fbshipit-source-id: d8015661fe27e656d34a210ddeec7789aed1802e
1 parent 9579d8f commit b1d61c3

9 files changed

Lines changed: 396 additions & 9 deletions

File tree

benchs/bench_rabitq_simd.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ void bench_rabitq_and_dot_product_with_sum(benchmark::State& state) {
8282
});
8383
}
8484

85+
void bench_rabitq_and_dot_product_with_sum_fused(benchmark::State& state) {
86+
bench_rabitq_generic(
87+
state,
88+
[](const uint8_t* q, const uint8_t* x, size_t size, size_t qb)
89+
-> int64_t {
90+
auto result = rabitq::bitwise_and_dot_product_with_popcount(
91+
q, x, size, qb);
92+
// Synthetic operation using both inputs for benchmarking.
93+
return result.popcount + result.dot_product;
94+
});
95+
}
96+
8597
template <SIMDLevel SL>
8698
void bench_rabitq_rearrange_impl(benchmark::State& state) {
8799
size_t qb = state.range(0);
@@ -127,6 +139,9 @@ BENCHMARK(bench_rabitq_xor_dot_product)
127139
BENCHMARK(bench_rabitq_and_dot_product_with_sum)
128140
->ArgsProduct({qbs, dims})
129141
->ArgNames({"qb", "d"});
142+
BENCHMARK(bench_rabitq_and_dot_product_with_sum_fused)
143+
->ArgsProduct({qbs, dims})
144+
->ArgNames({"qb", "d"});
130145
BENCHMARK(bench_rabitq_rearrange_scalar)
131146
->ArgsProduct({qbs, dims})
132147
->ArgNames({"qb", "d"});

faiss/impl/RaBitQuantizer.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -468,17 +468,16 @@ struct RaBitQDistanceComputerQ final : RaBitQDistanceComputer {
468468
qb);
469469
final_dot += int_dot * query_fac.int_dot_scale;
470470
} else {
471-
auto dot_qo = rabitq::bitwise_and_dot_product<SL>(
472-
rearranged_rotated_qq.data(), binary_data, size, qb);
473-
// It was a willful decision (after the discussion) to not to
474-
// pre-cache the sum of all bits, just in order to reduce the
475-
// overhead per vector.
476-
// process 64-bit popcounts
477-
auto sum_q = rabitq::popcount<SL>(binary_data, size);
471+
auto bitwise_result =
472+
rabitq::bitwise_and_dot_product_with_popcount<SL>(
473+
rearranged_rotated_qq.data(),
474+
binary_data,
475+
size,
476+
qb);
478477
// dot-product itself
479-
final_dot += query_fac.c1 * dot_qo;
478+
final_dot += query_fac.c1 * bitwise_result.dot_product;
480479
// normalizer coefficients
481-
final_dot += query_fac.c2 * sum_q;
480+
final_dot += query_fac.c2 * bitwise_result.popcount;
482481
// normalizer coefficients
483482
final_dot -= query_fac.c34;
484483
}

faiss/utils/rabitq_simd.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ namespace faiss::rabitq {
2121
constexpr int RABITQ_QUANTIZATION_SIMD_LEVELS = (1 << int(SIMDLevel::NONE)) |
2222
(1 << int(SIMDLevel::AVX2)) | (1 << int(SIMDLevel::AVX512));
2323

24+
struct BitwiseAndDotProductResult {
25+
uint64_t dot_product;
26+
uint64_t popcount;
27+
};
28+
2429
/**
2530
* Compute dot product between query and binary data using popcount on AND.
2631
*
@@ -37,6 +42,17 @@ uint64_t bitwise_and_dot_product(
3742
size_t size,
3843
size_t qb);
3944

45+
/**
46+
* Compute bitwise_and_dot_product(query, data, size, qb) and popcount(data,
47+
* size) in one pass over data.
48+
*/
49+
template <SIMDLevel SL = SINGLE_SIMD_LEVEL>
50+
BitwiseAndDotProductResult bitwise_and_dot_product_with_popcount(
51+
const uint8_t* query,
52+
const uint8_t* data,
53+
size_t size,
54+
size_t qb);
55+
4056
/**
4157
* Compute dot product between query and binary data using popcount on XOR.
4258
*
@@ -140,6 +156,35 @@ inline uint64_t bitwise_and_dot_product<SIMDLevel::NONE>(
140156
return sum;
141157
}
142158

159+
template <>
160+
inline BitwiseAndDotProductResult bitwise_and_dot_product_with_popcount<
161+
SIMDLevel::NONE>(
162+
const uint8_t* query,
163+
const uint8_t* data,
164+
size_t size,
165+
size_t qb) {
166+
uint64_t dot_product = 0;
167+
uint64_t popcount_sum = 0;
168+
size_t offset = 0;
169+
for (size_t step = 64 / 8; offset + step <= size; offset += step) {
170+
const auto yv = *(const uint64_t*)(data + offset);
171+
popcount_sum += popcount64(yv);
172+
for (int j = 0; j < qb; j++) {
173+
const auto qv = *(const uint64_t*)(query + j * size + offset);
174+
dot_product += popcount64(qv & yv) << j;
175+
}
176+
}
177+
for (; offset < size; ++offset) {
178+
const auto yv = *(data + offset);
179+
popcount_sum += popcount32(yv);
180+
for (int j = 0; j < qb; j++) {
181+
const auto qv = *(query + j * size + offset);
182+
dot_product += popcount32(qv & yv) << j;
183+
}
184+
}
185+
return {dot_product, popcount_sum};
186+
}
187+
143188
template <>
144189
inline uint64_t bitwise_xor_dot_product<SIMDLevel::NONE>(
145190
const uint8_t* query,

faiss/utils/simd_impl/rabitq_avx2.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,69 @@ uint64_t bitwise_and_dot_product<SIMDLevel::AVX2>(
291291
return sum;
292292
}
293293

294+
template <>
295+
BitwiseAndDotProductResult bitwise_and_dot_product_with_popcount<
296+
SIMDLevel::AVX2>(
297+
const uint8_t* query,
298+
const uint8_t* data,
299+
size_t size,
300+
size_t qb) {
301+
uint64_t dot_product = 0;
302+
uint64_t popcount_sum = 0;
303+
size_t offset = 0;
304+
if (size_t step = 256 / 8; offset + step <= size) {
305+
__m256i dot_256 = _mm256_setzero_si256();
306+
__m256i pop_256 = _mm256_setzero_si256();
307+
for (; offset + step <= size; offset += step) {
308+
__m256i v_x = _mm256_loadu_si256((const __m256i*)(data + offset));
309+
pop_256 = _mm256_add_epi64(pop_256, popcount_256(v_x));
310+
for (int j = 0; j < qb; j++) {
311+
__m256i v_q = _mm256_loadu_si256(
312+
(const __m256i*)(query + j * size + offset));
313+
__m256i v_and = _mm256_and_si256(v_q, v_x);
314+
__m256i v_popcnt = popcount_256(v_and);
315+
__m256i v_shifted = _mm256_slli_epi64(v_popcnt, j);
316+
dot_256 = _mm256_add_epi64(dot_256, v_shifted);
317+
}
318+
}
319+
dot_product += reduce_add_256(dot_256);
320+
popcount_sum += reduce_add_256(pop_256);
321+
}
322+
__m128i dot_128 = _mm_setzero_si128();
323+
__m128i pop_128 = _mm_setzero_si128();
324+
for (size_t step = 128 / 8; offset + step <= size; offset += step) {
325+
__m128i v_x = _mm_loadu_si128((const __m128i*)(data + offset));
326+
pop_128 = _mm_add_epi64(pop_128, popcount_128(v_x));
327+
for (int j = 0; j < qb; j++) {
328+
__m128i v_q = _mm_loadu_si128(
329+
(const __m128i*)(query + j * size + offset));
330+
__m128i v_and = _mm_and_si128(v_q, v_x);
331+
__m128i v_popcnt = popcount_128(v_and);
332+
__m128i v_shifted = _mm_slli_epi64(v_popcnt, j);
333+
dot_128 = _mm_add_epi64(dot_128, v_shifted);
334+
}
335+
}
336+
dot_product += reduce_add_128(dot_128);
337+
popcount_sum += reduce_add_128(pop_128);
338+
for (size_t step = 64 / 8; offset + step <= size; offset += step) {
339+
const uint64_t yv = *(const uint64_t*)(data + offset);
340+
popcount_sum += popcount64(yv);
341+
for (int j = 0; j < qb; j++) {
342+
const uint64_t qv = *(const uint64_t*)(query + j * size + offset);
343+
dot_product += popcount64(qv & yv) << j;
344+
}
345+
}
346+
for (; offset < size; ++offset) {
347+
const uint8_t yv = *(data + offset);
348+
popcount_sum += popcount32(yv);
349+
for (int j = 0; j < qb; j++) {
350+
const uint8_t qv = *(query + j * size + offset);
351+
dot_product += popcount32(qv & yv) << j;
352+
}
353+
}
354+
return {dot_product, popcount_sum};
355+
}
356+
294357
template <>
295358
uint64_t bitwise_xor_dot_product<SIMDLevel::AVX2>(
296359
const uint8_t* query,

faiss/utils/simd_impl/rabitq_avx512.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,87 @@ uint64_t bitwise_and_dot_product<SIMDLevel::AVX512>(
394394
return sum;
395395
}
396396

397+
template <>
398+
BitwiseAndDotProductResult bitwise_and_dot_product_with_popcount<
399+
SIMDLevel::AVX512>(
400+
const uint8_t* query,
401+
const uint8_t* data,
402+
size_t size,
403+
size_t qb) {
404+
uint64_t dot_product = 0;
405+
uint64_t popcount_sum = 0;
406+
size_t offset = 0;
407+
if (size_t step = 512 / 8; offset + step <= size) {
408+
__m512i dot_512 = _mm512_setzero_si512();
409+
__m512i pop_512 = _mm512_setzero_si512();
410+
for (; offset + step <= size; offset += step) {
411+
__m512i v_x = _mm512_loadu_si512((const __m512i*)(data + offset));
412+
pop_512 = _mm512_add_epi64(pop_512, popcount_512(v_x));
413+
for (int j = 0; j < qb; j++) {
414+
__m512i v_q = _mm512_loadu_si512(
415+
(const __m512i*)(query + j * size + offset));
416+
__m512i v_and = _mm512_and_si512(v_q, v_x);
417+
__m512i v_popcnt = popcount_512(v_and);
418+
__m512i v_shifted = _mm512_slli_epi64(v_popcnt, j);
419+
dot_512 = _mm512_add_epi64(dot_512, v_shifted);
420+
}
421+
}
422+
dot_product += _mm512_reduce_add_epi64(dot_512);
423+
popcount_sum += _mm512_reduce_add_epi64(pop_512);
424+
}
425+
if (size_t step = 256 / 8; offset + step <= size) {
426+
__m256i dot_256 = _mm256_setzero_si256();
427+
__m256i pop_256 = _mm256_setzero_si256();
428+
for (; offset + step <= size; offset += step) {
429+
__m256i v_x = _mm256_loadu_si256((const __m256i*)(data + offset));
430+
pop_256 = _mm256_add_epi64(pop_256, popcount_256(v_x));
431+
for (int j = 0; j < qb; j++) {
432+
__m256i v_q = _mm256_loadu_si256(
433+
(const __m256i*)(query + j * size + offset));
434+
__m256i v_and = _mm256_and_si256(v_q, v_x);
435+
__m256i v_popcnt = popcount_256(v_and);
436+
__m256i v_shifted = _mm256_slli_epi64(v_popcnt, j);
437+
dot_256 = _mm256_add_epi64(dot_256, v_shifted);
438+
}
439+
}
440+
dot_product += reduce_add_256(dot_256);
441+
popcount_sum += reduce_add_256(pop_256);
442+
}
443+
__m128i dot_128 = _mm_setzero_si128();
444+
__m128i pop_128 = _mm_setzero_si128();
445+
for (size_t step = 128 / 8; offset + step <= size; offset += step) {
446+
__m128i v_x = _mm_loadu_si128((const __m128i*)(data + offset));
447+
pop_128 = _mm_add_epi64(pop_128, popcount_128(v_x));
448+
for (int j = 0; j < qb; j++) {
449+
__m128i v_q = _mm_loadu_si128(
450+
(const __m128i*)(query + j * size + offset));
451+
__m128i v_and = _mm_and_si128(v_q, v_x);
452+
__m128i v_popcnt = popcount_128(v_and);
453+
__m128i v_shifted = _mm_slli_epi64(v_popcnt, j);
454+
dot_128 = _mm_add_epi64(dot_128, v_shifted);
455+
}
456+
}
457+
dot_product += reduce_add_128(dot_128);
458+
popcount_sum += reduce_add_128(pop_128);
459+
for (size_t step = 64 / 8; offset + step <= size; offset += step) {
460+
const auto yv = *(const uint64_t*)(data + offset);
461+
popcount_sum += popcount64(yv);
462+
for (int j = 0; j < qb; j++) {
463+
const auto qv = *(const uint64_t*)(query + j * size + offset);
464+
dot_product += popcount64(qv & yv) << j;
465+
}
466+
}
467+
for (; offset < size; ++offset) {
468+
const auto yv = *(data + offset);
469+
popcount_sum += popcount32(yv);
470+
for (int j = 0; j < qb; j++) {
471+
const auto qv = *(query + j * size + offset);
472+
dot_product += popcount32(qv & yv) << j;
473+
}
474+
}
475+
return {dot_product, popcount_sum};
476+
}
477+
397478
template <>
398479
uint64_t bitwise_xor_dot_product<SIMDLevel::AVX512>(
399480
const uint8_t* query,

faiss/utils/simd_impl/rabitq_avx512_spr.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,98 @@ uint64_t bitwise_and_dot_product<SIMDLevel::AVX512_SPR>(
185185
return sum;
186186
}
187187

188+
template <>
189+
BitwiseAndDotProductResult bitwise_and_dot_product_with_popcount<
190+
SIMDLevel::AVX512_SPR>(
191+
const uint8_t* query,
192+
const uint8_t* data,
193+
size_t size,
194+
size_t qb) {
195+
uint64_t dot_product = 0;
196+
uint64_t popcount_sum = 0;
197+
size_t offset = 0;
198+
199+
if (size_t step = 512 / 8; offset + step <= size) {
200+
__m512i dot_512 = _mm512_setzero_si512();
201+
__m512i pop_512 = _mm512_setzero_si512();
202+
for (; offset + step <= size; offset += step) {
203+
__m512i v_x = _mm512_loadu_si512(
204+
reinterpret_cast<const __m512i*>(data + offset));
205+
pop_512 = _mm512_add_epi64(pop_512, popcount_512_vpopcntdq(v_x));
206+
for (size_t j = 0; j < qb; j++) {
207+
__m512i v_q = _mm512_loadu_si512(
208+
reinterpret_cast<const __m512i*>(
209+
query + j * size + offset));
210+
__m512i v_and = _mm512_and_si512(v_q, v_x);
211+
__m512i v_popcnt = popcount_512_vpopcntdq(v_and);
212+
__m512i v_shifted = _mm512_slli_epi64(v_popcnt, j);
213+
dot_512 = _mm512_add_epi64(dot_512, v_shifted);
214+
}
215+
}
216+
dot_product += _mm512_reduce_add_epi64(dot_512);
217+
popcount_sum += _mm512_reduce_add_epi64(pop_512);
218+
}
219+
220+
if (size_t step = 256 / 8; offset + step <= size) {
221+
__m256i dot_256 = _mm256_setzero_si256();
222+
__m256i pop_256 = _mm256_setzero_si256();
223+
for (; offset + step <= size; offset += step) {
224+
__m256i v_x = _mm256_loadu_si256(
225+
reinterpret_cast<const __m256i*>(data + offset));
226+
pop_256 = _mm256_add_epi64(pop_256, popcount_256_vpopcntdq(v_x));
227+
for (size_t j = 0; j < qb; j++) {
228+
__m256i v_q = _mm256_loadu_si256(
229+
reinterpret_cast<const __m256i*>(
230+
query + j * size + offset));
231+
__m256i v_and = _mm256_and_si256(v_q, v_x);
232+
__m256i v_popcnt = popcount_256_vpopcntdq(v_and);
233+
__m256i v_shifted = _mm256_slli_epi64(v_popcnt, j);
234+
dot_256 = _mm256_add_epi64(dot_256, v_shifted);
235+
}
236+
}
237+
dot_product += reduce_add_256(dot_256);
238+
popcount_sum += reduce_add_256(pop_256);
239+
}
240+
241+
__m128i dot_128 = _mm_setzero_si128();
242+
__m128i pop_128 = _mm_setzero_si128();
243+
for (size_t step = 128 / 8; offset + step <= size; offset += step) {
244+
__m128i v_x = _mm_loadu_si128(
245+
reinterpret_cast<const __m128i*>(data + offset));
246+
pop_128 = _mm_add_epi64(pop_128, popcount_128_vpopcntdq(v_x));
247+
for (size_t j = 0; j < qb; j++) {
248+
__m128i v_q = _mm_loadu_si128(
249+
reinterpret_cast<const __m128i*>(
250+
query + j * size + offset));
251+
__m128i v_and = _mm_and_si128(v_q, v_x);
252+
__m128i v_popcnt = popcount_128_vpopcntdq(v_and);
253+
__m128i v_shifted = _mm_slli_epi64(v_popcnt, j);
254+
dot_128 = _mm_add_epi64(dot_128, v_shifted);
255+
}
256+
}
257+
dot_product += reduce_add_128(dot_128);
258+
popcount_sum += reduce_add_128(pop_128);
259+
260+
for (size_t step = 64 / 8; offset + step <= size; offset += step) {
261+
const auto yv = *reinterpret_cast<const uint64_t*>(data + offset);
262+
popcount_sum += popcount64(yv);
263+
for (size_t j = 0; j < qb; j++) {
264+
const auto qv = *reinterpret_cast<const uint64_t*>(
265+
query + j * size + offset);
266+
dot_product += static_cast<uint64_t>(popcount64(qv & yv)) << j;
267+
}
268+
}
269+
for (; offset < size; ++offset) {
270+
const auto yv = *(data + offset);
271+
popcount_sum += popcount32(yv);
272+
for (size_t j = 0; j < qb; j++) {
273+
const auto qv = *(query + j * size + offset);
274+
dot_product += static_cast<uint64_t>(popcount32(qv & yv)) << j;
275+
}
276+
}
277+
return {dot_product, popcount_sum};
278+
}
279+
188280
template <>
189281
uint64_t bitwise_xor_dot_product<SIMDLevel::AVX512_SPR>(
190282
const uint8_t* query,

faiss/utils/simd_impl/rabitq_neon.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ uint64_t bitwise_and_dot_product<SIMDLevel::ARM_NEON>(
2020
return bitwise_and_dot_product<SIMDLevel::NONE>(query, data, size, qb);
2121
}
2222

23+
template <>
24+
BitwiseAndDotProductResult bitwise_and_dot_product_with_popcount<
25+
SIMDLevel::ARM_NEON>(
26+
const uint8_t* query,
27+
const uint8_t* data,
28+
size_t size,
29+
size_t qb) {
30+
return bitwise_and_dot_product_with_popcount<SIMDLevel::NONE>(
31+
query, data, size, qb);
32+
}
33+
2334
template <>
2435
uint64_t bitwise_xor_dot_product<SIMDLevel::ARM_NEON>(
2536
const uint8_t* query,

0 commit comments

Comments
 (0)