cuDDL
Loading...
Searching...
No Matches
kernels.cuh
Go to the documentation of this file.
1#pragma once
2
3#include <cooperative_groups.h>
4#include <cooperative_groups/reduce.h>
5#include <cuda_runtime.h>
6#include <cub/block/block_histogram.cuh>
7#include <cub/block/block_reduce.cuh>
8#include <cub/block/block_scan.cuh>
9#include <cuda/std/algorithm>
10#include <cuda/std/bit>
11#include <cuda/std/cstddef>
12#include <cuda/std/cstdint>
13
14#include <cuddl/blacklist.cuh>
17#include <cuddl/detail/hash.cuh>
20#include <cuddl/helpers.cuh>
22
23namespace cg = cooperative_groups;
24
25namespace cuddl::detail {
26
28constexpr uint32_t block_size = 256;
29
31constexpr size_t maximum_batch_rows = (size_t{1} << 31) - 1U;
32
36 uint64_t empty{};
37 double restored_sum{};
38
40 __device__ summary_payload& operator+=(summary_payload const& other) noexcept {
41 counts += other.counts;
42 empty += other.empty;
43 restored_sum += other.restored_sum;
44 return *this;
45 }
46
48 friend __device__ summary_payload
49 operator+(summary_payload left, summary_payload const& right) noexcept {
50 return left += right;
51 }
52};
53
54namespace {
55
57template <bool IncludeCardinality>
58__device__ summary_payload combine_payloads(summary_payload a, summary_payload const& b) noexcept {
59 a.counts += b.counts;
60 if constexpr (IncludeCardinality) {
61 a.empty += b.empty;
62 a.restored_sum += b.restored_sum;
63 }
64 return a;
65}
66
67} // namespace
68
73template <typename QueryScore, typename ReferenceScore>
74constexpr uint32_t wide_chunk_buckets =
75 (sizeof(QueryScore) == 2U || sizeof(ReferenceScore) == 2U) ? 16U : 8U;
76
78constexpr uint32_t shared_construction_block_size = 768;
79
90template <size_t BucketCount, typename Layout = default_register_layout, bool HasBlacklist = false>
91__global__ __launch_bounds__(shared_construction_block_size) void add_shared_kernel(
92 uint64_t const* input,
93 size_t input_size,
94 uint32_t* registers,
97) {
98 __shared__ uint32_t state[BucketCount];
99 for (auto i = threadIdx.x; i < BucketCount; i += blockDim.x) {
100 state[i] = 0U;
101 }
103
104 auto const process = [&](uint64_t value) {
105 auto const hash = hash_kmer(value);
106 auto const incoming = static_cast<uint32_t>(score<Layout>(hash));
107 auto const bucket = bucket_of<BucketCount>(hash);
108 if constexpr (HasBlacklist) {
109 if (blacklist.contains(value, hash, bucket)) return;
110 }
111 atomicMax(&state[bucket], incoming);
112 };
113
114 auto const stride = static_cast<size_t>(gridDim.x) * blockDim.x * 4U;
115 auto const index = (static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x) * 4U;
116 for (auto offset = index; offset < input_size; offset += stride) {
117 if (vector_input && offset + 4U <= input_size) {
118 uint64_t values[4];
119 load_256_global_nc(input + offset, values);
120 _Pragma("unroll")
121 for (uint32_t item = 0; item < 4U; ++item) {
122 process(values[item]);
123 }
124 } else {
125 // Scalar path: unaligned input keeps striding exactly like the vector path; only
126 // the aligned input's final partial chunk (fewer than four items) ends the loop.
127 _Pragma("unroll")
128 for (uint32_t item = 0; item < 4U; ++item) {
129 if (offset + item < input_size) {
130 process(input[offset + item]);
131 }
132 }
133 if (vector_input) {
134 break;
135 }
136 }
137 }
139
140 // Each CTA starts its merge at a rotated bucket offset (odd multiplier, coprime with
141 // the power-of-two bucket count), so the CTAs' atomic merges interleave across different
142 // addresses instead of all colliding on the same bucket at once. The per-address atomic
143 // serialization then sees a smooth stream of arrivals rather than a synchronized burst.
144 auto const bucket_offset = (static_cast<size_t>(blockIdx.x) * 139U) & (BucketCount - 1U);
145 for (auto j = threadIdx.x; j < BucketCount; j += blockDim.x) {
146 auto const i = (static_cast<size_t>(j) + bucket_offset) & (BucketCount - 1U);
147 auto const stored = state[i];
148 if (stored != 0U) atomicMax(&registers[i], stored);
149 }
150}
151
155template <size_t BucketCount, bool IncludeCardinality, typename Layout = default_register_layout>
156__global__ void
157summary_kernel(uint32_t const* left, uint32_t const* right, pairwise_summary& output) {
158 using block_reduce = cub::BlockReduce<summary_payload, block_size>;
159 __shared__ typename block_reduce::TempStorage storage;
160
162 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
163 bucket += block_size) {
164 auto const left_reg = left[bucket];
165 auto const right_reg = right[bucket];
166 classify(local.counts, left_reg, right_reg);
167 if constexpr (IncludeCardinality) {
168 if (left_reg == 0U) {
169 ++local.empty;
170 } else {
171 local.restored_sum += restore_midpoint<Layout>(static_cast<uint16_t>(left_reg));
172 }
173 }
174 }
175 auto const total = block_reduce(storage).Reduce(local, combine_payloads<IncludeCardinality>);
176 if (threadIdx.x == 0) {
177 output.counts = total.counts;
178 if constexpr (IncludeCardinality) {
179 output.cardinality = cardinality(
180 static_cast<double>(BucketCount),
181 static_cast<double>(total.empty),
182 total.restored_sum
183 );
184 }
185 }
186}
187
193template <size_t BucketCount>
194__global__ __launch_bounds__(block_size) void batch_summary_kernel(
195 uint32_t const* left_rows,
196 uint32_t const* right_rows,
197 size_t pair_count,
198 pairwise_summary* outputs
199) {
200 constexpr uint32_t warp_width = 32U;
201 constexpr uint32_t warps_per_block = block_size / warp_width;
202 using warp_reduce = cub::WarpReduce<pairwise_counts>;
203 __shared__ typename warp_reduce::TempStorage storage[warps_per_block];
204
205 auto const warp = static_cast<uint32_t>(threadIdx.x) / warp_width;
206 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
207 auto pair = static_cast<size_t>(blockIdx.x) * warps_per_block + warp;
208 auto const pair_stride = static_cast<size_t>(gridDim.x) * warps_per_block;
209 for (; pair < pair_count; pair += pair_stride) {
210 auto const row_offset = pair * BucketCount;
212 for (auto bucket = static_cast<size_t>(lane); bucket < BucketCount; bucket += warp_width) {
213 classify(local, left_rows[row_offset + bucket], right_rows[row_offset + bucket]);
214 }
215 auto const total = warp_reduce(storage[warp]).Sum(local);
216 if (lane == 0U) {
217 outputs[pair].counts = total;
218 outputs[pair].cardinality = 0.0;
219 }
220 __syncwarp();
221 }
222}
223
227inline __device__ pairwise_counts
228reduce_warp(cg::thread_block_tile<32> const warp, pairwise_counts counts) {
229 counts.lower = cg::reduce(warp, counts.lower, cg::plus<uint32_t>{});
230 counts.equal = cg::reduce(warp, counts.equal, cg::plus<uint32_t>{});
231 counts.higher = cg::reduce(warp, counts.higher, cg::plus<uint32_t>{});
232 counts.both_empty = cg::reduce(warp, counts.both_empty, cg::plus<uint32_t>{});
233 return counts;
234}
235
242template <typename Row>
243__global__ void transpose_indexed_scores_kernel(
244 Row const* rows,
245 uint32_t reference_count,
246 uint32_t indexed_bucket_count,
247 uint32_t full_bucket_count,
248 Row* transposed
249) {
250 constexpr uint32_t tile = 32U;
251 __shared__ Row staging[tile][tile + 1U];
252 auto const bucket = static_cast<uint32_t>(blockIdx.x) * tile + threadIdx.x;
253 auto const reference = static_cast<uint32_t>(blockIdx.y) * tile + threadIdx.y;
254 if (bucket < indexed_bucket_count && reference < reference_count) {
255 staging[threadIdx.y][threadIdx.x] =
256 rows[static_cast<size_t>(reference) * full_bucket_count + bucket];
257 }
259 // Output (bucket, reference) = input (reference, bucket). Threads with consecutive
260 // threadIdx.x must write consecutive references of one bucket, so the output bucket index
261 // comes from threadIdx.y and the output reference from threadIdx.x; the shared-memory read
262 // is the transposed (column) access instead.
263 auto const out_bucket = static_cast<uint32_t>(blockIdx.x) * tile + threadIdx.y;
264 auto const out_reference = static_cast<uint32_t>(blockIdx.y) * tile + threadIdx.x;
265 if (out_bucket < indexed_bucket_count && out_reference < reference_count) {
266 transposed[static_cast<size_t>(out_bucket) * reference_count + out_reference] =
267 staging[threadIdx.x][threadIdx.y];
268 }
269}
270
278template <typename Row>
279__global__ void count_index_cells_bucket_kernel(
280 Row const* transposed,
281 uint32_t indexed_bucket_count,
282 uint32_t reference_count,
283 uint16_t key_mask,
284 uint32_t* cell_counts
285) {
286 extern __shared__ uint32_t key_counts[];
287 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
288 auto const quarter_keys = key_count / 4U;
289 // The exclusive scan also reads the trailing element used for the CSR end offset.
290 if (blockIdx.x == 0U && threadIdx.x == 0U) {
291 cell_counts[static_cast<size_t>(indexed_bucket_count) * key_count] = 0U;
292 }
293 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
294 key_counts[i] = 0U;
295 }
297
298 for (auto bucket = blockIdx.x; bucket < indexed_bucket_count; bucket += gridDim.x) {
299 auto const* bucket_scores = transposed + static_cast<size_t>(bucket) * reference_count;
300 auto* bucket_counts = cell_counts + static_cast<size_t>(bucket) * key_count;
301 for (uint32_t quarter = 0U; quarter < 4U; ++quarter) {
302 auto const key_base = quarter * quarter_keys;
303 for (uint32_t reference = threadIdx.x; reference < reference_count;
304 reference += blockDim.x) {
305 auto const score = static_cast<uint16_t>(__ldcs(&bucket_scores[reference]));
306 if (score == 0U) {
307 continue;
308 }
309 auto const key = static_cast<uint32_t>(score & key_mask);
310 if (key < key_base || key >= key_base + quarter_keys) {
311 continue;
312 }
313 atomicAdd(&key_counts[key - key_base], 1U);
314 }
316 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
317 bucket_counts[key_base + i] = key_counts[i];
318 key_counts[i] = 0U;
319 }
321 }
322 }
323}
324
334template <typename Row>
335__global__ void scatter_index_postings_bucket_kernel(
336 Row const* transposed,
337 uint32_t indexed_bucket_count,
338 uint32_t reference_count,
339 uint16_t key_mask,
340 uint32_t const* offsets,
341 uint32_t* postings
342) {
343 extern __shared__ uint32_t key_ranks[];
344 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
345 auto const quarter_keys = key_count / 4U;
346 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
347 key_ranks[i] = 0U;
348 }
350
351 for (auto bucket = blockIdx.x; bucket < indexed_bucket_count; bucket += gridDim.x) {
352 auto const* bucket_scores = transposed + static_cast<size_t>(bucket) * reference_count;
353 auto const* bucket_offsets = offsets + static_cast<size_t>(bucket) * key_count;
354 for (uint32_t quarter = 0U; quarter < 4U; ++quarter) {
355 auto const key_base = quarter * quarter_keys;
356 for (uint32_t reference = threadIdx.x; reference < reference_count;
357 reference += blockDim.x) {
358 auto const score = static_cast<uint16_t>(__ldcs(&bucket_scores[reference]));
359 if (score == 0U) {
360 continue;
361 }
362 auto const key = static_cast<uint32_t>(score & key_mask);
363 if (key < key_base || key >= key_base + quarter_keys) {
364 continue;
365 }
366 auto const rank = atomicAdd(&key_ranks[key - key_base], 1U);
367 __stcs(&postings[bucket_offsets[key] + rank], reference);
368 }
370 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
371 key_ranks[i] = 0U;
372 }
374 }
375 }
376}
377
379struct minimum_match_predicate {
380 uint32_t const* match_counts;
381 uint32_t minimum_matches;
382
383 [[nodiscard]] __host__ __device__ bool operator()(uint32_t reference_id) const noexcept {
384 return match_counts[reference_id] >= minimum_matches;
385 }
386};
387
392constexpr uint32_t index_match_cells_per_warp = 32U;
393
395__device__ inline uint2 lane_posting_range(
396 uint32_t const* offsets,
397 uint16_t const* sorted_keys,
398 uint32_t reference_count,
399 uint32_t bucket,
400 uint32_t key,
401 uint32_t key_count
402) {
403 if (sorted_keys == nullptr) {
404 auto const cell = static_cast<size_t>(bucket) * key_count + key;
405 return {offsets[cell], offsets[cell + 1U]};
406 }
407 auto const* first = sorted_keys + static_cast<size_t>(bucket) * reference_count;
408 auto const sparse_key = static_cast<uint16_t>(key + (key_count == 32768U));
409 auto const range = cuda::std::equal_range(first, first + reference_count, sparse_key);
410 return {
411 static_cast<uint32_t>(range.first - sorted_keys),
412 static_cast<uint32_t>(range.second - sorted_keys)
413 };
414}
415
420__host__ __device__ constexpr uint32_t sparse_directory_shift(uint32_t reference_count) noexcept {
421 uint32_t cell_bits = 0U;
422 while (cell_bits < 16U && (uint64_t{1} << (cell_bits + 1U)) * 8U <= reference_count) {
423 ++cell_bits;
424 }
425 return 16U - cell_bits;
426}
427
429__host__ __device__ constexpr uint32_t sparse_directory_entries(uint32_t reference_count) noexcept {
430 return (1U << (16U - sparse_directory_shift(reference_count))) + 1U;
431}
432
435__global__ __launch_bounds__(block_size) static void build_sparse_directory_kernel(
436 uint16_t const* sorted_keys,
439 uint32_t* directory
440) {
441 auto const entries = sparse_directory_entries(reference_count);
443 auto const total = static_cast<size_t>(indexed_bucket_count) * entries;
444 for (auto i = static_cast<size_t>(blockIdx.x) * block_size + threadIdx.x; i < total;
445 i += static_cast<size_t>(gridDim.x) * block_size) {
446 auto const bucket = i / entries;
447 auto const bound = static_cast<uint32_t>(i % entries) << shift;
448 auto const* first = sorted_keys + bucket * reference_count;
449 directory[i] = static_cast<uint32_t>(
450 cuda::std::lower_bound(
451 first,
452 first + reference_count,
453 bound,
454 [](uint16_t key, uint32_t value) { return key < value; }
455 ) -
456 first
457 );
458 }
459}
460
467__global__ __launch_bounds__(block_size) static void index_pair_work_kernel(
468 uint32_t const* offsets,
469 uint16_t const* sorted_keys,
470 uint32_t reference_count,
471 uint32_t indexed_bucket_count,
472 uint32_t key_count,
473 unsigned long long* work
474) {
475 auto const total = offsets != nullptr
476 ? static_cast<size_t>(indexed_bucket_count) * key_count
477 : static_cast<size_t>(indexed_bucket_count) * reference_count;
478 unsigned long long local = 0U;
479 for (auto i = static_cast<size_t>(blockIdx.x) * block_size + threadIdx.x; i < total;
480 i += static_cast<size_t>(gridDim.x) * block_size) {
481 uint64_t length = 0U;
482 if (offsets != nullptr) {
483 if (i % key_count != 0U) {
484 length = offsets[i + 1U] - offsets[i];
485 }
486 } else {
487 auto const position = static_cast<uint32_t>(i % reference_count);
488 auto const* first = sorted_keys + (i - position);
489 auto const key = first[position];
490 if (key != 0U && (position == 0U || first[position - 1U] != key)) {
491 length = static_cast<uint64_t>(
492 cuda::std::upper_bound(first + position, first + reference_count, key) -
493 (first + position)
494 );
495 }
496 }
497 local += length * length;
498 }
499 if (local != 0U) {
500 atomicAdd(work, local);
501 }
502}
503
505__device__ inline uint2 directory_posting_range(
506 uint16_t const* sorted_keys,
507 uint32_t const* directory,
508 uint32_t reference_count,
509 uint32_t bucket,
510 uint32_t sparse_key
511) {
512 auto const* first = sorted_keys + static_cast<size_t>(bucket) * reference_count;
513 auto const* cells =
514 directory + static_cast<size_t>(bucket) * sparse_directory_entries(reference_count);
515 auto const cell = sparse_key >> sparse_directory_shift(reference_count);
516 auto const* low = first + cells[cell];
517 auto const* high = first + cells[cell + 1U];
518 auto const less = [](uint16_t key, uint32_t value) {
519 return key < value;
520 };
521 auto const* begin = cuda::std::lower_bound(low, high, sparse_key, less);
522 auto const* end = cuda::std::lower_bound(begin, high, sparse_key + 1U, less);
523 return {static_cast<uint32_t>(begin - sorted_keys), static_cast<uint32_t>(end - sorted_keys)};
524}
525
527constexpr uint32_t posting_range_block_size = 1024U;
528
534static __global__ __launch_bounds__(posting_range_block_size) void index_posting_ranges_kernel(
535 uint16_t const* query,
536 uint32_t const* offsets,
537 uint16_t const* sorted_keys,
538 uint32_t reference_count,
539 uint32_t indexed_bucket_count,
540 uint16_t key_mask,
541 uint32_t* range_begin,
543) {
544 using block_scan = cub::BlockScan<uint32_t, posting_range_block_size>;
545 __shared__ typename block_scan::TempStorage storage;
546 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
547 uint32_t carry = 0U;
548 for (uint32_t base = 0U; base < indexed_bucket_count; base += posting_range_block_size) {
549 auto const bucket = base + static_cast<uint32_t>(threadIdx.x);
550 auto const score = bucket < indexed_bucket_count ? query[bucket] : uint16_t{0};
551 uint2 range{0U, 0U};
552 if (score != 0U) {
553 range = lane_posting_range(
555 );
556 }
557 uint32_t end = 0U;
558 uint32_t aggregate = 0U;
559 block_scan(storage).InclusiveSum(range.y - range.x, end, aggregate);
560 if (bucket < indexed_bucket_count) {
561 range_begin[bucket] = range.x;
562 range_end_scan[bucket] = carry + end;
563 }
564 carry += aggregate;
566 }
567}
568
570static __global__ __launch_bounds__(block_size) void count_balanced_index_matches_kernel(
571 uint32_t const* range_begin,
572 uint32_t const* range_end_scan,
573 uint32_t indexed_bucket_count,
574 uint32_t const* postings,
575 uint32_t* match_counts
576) {
577 auto const total = range_end_scan[indexed_bucket_count - 1U];
578 auto const stride = static_cast<uint32_t>(gridDim.x) * block_size;
579 for (auto item = static_cast<uint32_t>(blockIdx.x) * block_size + threadIdx.x; item < total;
580 item += stride) {
581 auto const bucket = static_cast<uint32_t>(
582 cuda::std::upper_bound(range_end_scan, range_end_scan + indexed_bucket_count, item) -
584 );
585 auto const first_item = bucket == 0U ? 0U : range_end_scan[bucket - 1U];
586 atomicAdd(&match_counts[postings[range_begin[bucket] + (item - first_item)]], 1U);
587 }
588}
589
596template <size_t BucketCount, typename QueryRow>
597__global__ __launch_bounds__(block_size) void count_batch_index_matches_kernel(
598 QueryRow const* queries,
600 uint32_t query_count,
601 uint32_t const* offsets,
602 uint32_t const* postings,
603 uint32_t reference_count,
604 uint32_t indexed_bucket_count,
605 uint16_t key_mask,
606 uint32_t* match_counts,
607 uint16_t const* sorted_keys = nullptr
608) {
609 constexpr uint32_t warp_width = 32;
610 constexpr uint32_t warps_per_block = block_size / warp_width;
611 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
612 auto const warp = static_cast<uint32_t>(threadIdx.x) / warp_width;
613 auto const total_cells = static_cast<uint64_t>(query_count) * indexed_bucket_count;
614 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
615 // The indexed bucket count is always a power of two (BucketCount or BucketCount / 2), so
616 // splitting the linear cell id costs a shift and a mask instead of a 64-bit division.
617 auto const bucket_shift = static_cast<uint32_t>(cuda::std::countr_zero(indexed_bucket_count));
618 auto const bucket_mask = indexed_bucket_count - 1U;
619 auto const first_cell =
620 (static_cast<uint64_t>(blockIdx.x) * warps_per_block + warp) * index_match_cells_per_warp;
621 auto const warp_cell_stride =
622 static_cast<uint64_t>(gridDim.x) * warps_per_block * index_match_cells_per_warp;
623 for (auto cell_base = first_cell; cell_base < total_cells; cell_base += warp_cell_stride) {
624 auto const cell = cell_base + lane;
625 auto const query_index = static_cast<uint32_t>(cell >> bucket_shift);
626 auto const bucket = static_cast<uint32_t>(cell) & bucket_mask;
627 auto const score =
628 cell < total_cells
629 ? static_cast<uint16_t>(
630 queries[(query_row_offset + query_index) * BucketCount + bucket]
631 )
632 : 0U;
633 uint2 range{0U, 0U};
634 if (score != 0U) {
635 range = lane_posting_range(
636 offsets,
639 bucket,
640 static_cast<uint32_t>(score & key_mask),
642 );
643 }
644 auto pending = __ballot_sync(0xffffffffU, range.x < range.y);
645 while (pending != 0U) {
646 auto const source = static_cast<uint32_t>(__ffs(pending) - 1);
647 pending &= pending - 1U;
648 auto const end = __shfl_sync(0xffffffffU, range.y, source);
649 auto posting = __shfl_sync(0xffffffffU, range.x, source) + lane;
650 auto* const counts =
651 match_counts + static_cast<size_t>(__shfl_sync(0xffffffffU, query_index, source)) *
653 // Four independent posting loads in flight per lane keep the atomic stream fed
654 // while the following loads are still outstanding.
655 for (; posting + 3U * warp_width < end; posting += 4U * warp_width) {
656 uint32_t ids[4];
657 _Pragma("unroll")
658 for (uint32_t j = 0U; j < 4U; ++j) {
659 ids[j] = postings[posting + j * warp_width];
660 }
661 _Pragma("unroll")
662 for (uint32_t j = 0U; j < 4U; ++j) {
663 atomicAdd(&counts[ids[j]], 1U);
664 }
665 }
666 for (; posting < end; posting += warp_width) {
667 atomicAdd(&counts[postings[posting]], 1U);
668 }
669 }
670 }
671}
672
674__host__ __device__ constexpr uint32_t candidate_bit_words(uint32_t reference_count) noexcept {
675 return (reference_count + 31U) / 32U;
676}
677
679constexpr uint32_t index_tile_block_size = 1024U;
680
682constexpr uint32_t index_tile_lane_postings = 16U;
683
685constexpr uint32_t index_tile_long_capacity = 32U;
686
691[[nodiscard]] inline bool uses_tiled_index_counts(
692 uint32_t reference_count,
693 uint32_t query_count,
694 uint32_t indexed_bucket_count
695) noexcept {
696 return reference_count != 0U && indexed_bucket_count <= 0xffffU &&
697 (reference_count >= 8192U || query_count >= 512U);
698}
699
702[[nodiscard]] inline uint32_t index_tile_references(
703 uint32_t reference_count,
704 uint32_t query_count,
705 uint32_t multiprocessors,
706 uint32_t max_shared_bytes
707) noexcept {
708 constexpr uint32_t static_bytes = index_tile_long_capacity * sizeof(uint2) + sizeof(uint32_t);
709 auto const max_references = (max_shared_bytes - static_bytes) / (64U * sizeof(uint16_t)) * 64U;
710 auto const needed = (reference_count + max_references - 1U) / max_references;
711 auto const filling = (4U * multiprocessors + query_count - 1U) / query_count;
712 auto const tiles = needed > filling ? needed : filling;
713 return ((reference_count + tiles - 1U) / tiles + 63U) / 64U * 64U;
714}
715
718 uint32_t const* offsets;
719 uint16_t const* sorted_keys;
721 uint32_t words;
722 __device__ bool operator()(uint32_t cell) const {
723 if (sorted_keys == nullptr) {
724 return offsets[cell + 1U] - offsets[cell] >= words;
725 }
726 auto const position = cell % reference_count;
727 auto const key = sorted_keys[cell];
728 return key != 0U && (position == 0U || sorted_keys[cell - 1U] != key) &&
729 words <= reference_count - position && sorted_keys[cell + words - 1U] == key;
730 }
731};
732
735static __global__ void build_posting_bitmaps_kernel(
736 uint32_t* cells,
737 uint32_t const* offsets,
738 uint16_t const* sorted_keys,
739 uint32_t reference_count,
740 uint32_t key_count,
741 uint32_t const* postings,
742 uint32_t words,
743 uint32_t* bitmaps
744) {
745 __shared__ uint2 range;
746 if (threadIdx.x == 0U) {
747 auto const cell = cells[blockIdx.x];
748 if (sorted_keys == nullptr) {
749 range = {offsets[cell], offsets[cell + 1U]};
750 } else {
751 auto const bucket = cell / reference_count;
752 auto const key = sorted_keys[cell];
753 auto const* end = sorted_keys + static_cast<size_t>(bucket + 1U) * reference_count;
754 range = {
755 cell,
756 static_cast<uint32_t>(
757 cuda::std::upper_bound(sorted_keys + cell, end, key) - sorted_keys
758 )
759 };
760 cells[blockIdx.x] = bucket * key_count + key - (key_count == 32768U);
761 }
762 }
764 auto* bitmap = bitmaps + static_cast<size_t>(blockIdx.x) * words;
765 for (auto i = static_cast<size_t>(range.x) + threadIdx.x; i < range.y; i += blockDim.x) {
766 auto const reference = postings[i];
767 atomicOr(bitmap + reference / 32U, 1U << (reference % 32U));
768 }
769}
770
774template <size_t BucketCount>
776 uint16_t const* queries,
777 uint32_t const* offsets,
778 uint32_t const* bitmap_cells,
779 uint32_t bitmap_count,
780 uint32_t words,
781 uint32_t indexed_buckets,
782 uint16_t key_mask,
783 uint2* ranges,
784 uint32_t* bitmap_starts,
785 uint32_t* bitmap_counts
786) {
787 using block_scan = cub::BlockScan<uint32_t, block_size>;
788 __shared__ typename block_scan::TempStorage storage;
789 uint32_t count = 0U;
790 auto const query = static_cast<uint32_t>(blockIdx.x);
791 auto const base = static_cast<size_t>(query) * indexed_buckets;
792 for (uint32_t first = 0U; first < indexed_buckets; first += block_size) {
793 auto const bucket = first + threadIdx.x;
794 uint2 range{0U, 0U};
795 uint32_t bitmap = 0U;
796 bool selected = false;
797 auto const score = bucket < indexed_buckets
798 ? queries[static_cast<size_t>(query) * BucketCount + bucket]
799 : uint16_t{0};
800 if (score != 0U) {
801 auto const cell = bucket * (static_cast<uint32_t>(key_mask) + 1U) + (score & key_mask);
802 if (offsets != nullptr) {
803 range = {offsets[cell], offsets[cell + 1U]};
804 } else {
805 range = ranges[base + bucket];
806 }
807 if (range.y - range.x >= words) {
808 uint32_t low = 0U, high = bitmap_count;
809 while (low < high) {
810 auto const middle = low + (high - low) / 2U;
811 if (bitmap_cells[middle] < cell) {
812 low = middle + 1U;
813 } else {
814 high = middle;
815 }
816 }
817 bitmap = low * words;
818 selected = true;
819 range = {0U, 0U};
820 }
821 }
822 uint32_t position = 0U, aggregate = 0U;
823 block_scan(storage).ExclusiveSum(static_cast<uint32_t>(selected), position, aggregate);
824 if (selected) {
825 bitmap_starts[base + count + position] = bitmap;
826 }
827 if (bucket < indexed_buckets) {
828 ranges[base + bucket] = range;
829 }
830 count += aggregate;
832 }
833 if (threadIdx.x == 0U) {
835 }
836}
837
843template <uint32_t CounterBits>
844__device__ uint32_t sum_posting_bitmaps(
845 uint32_t const* bitmaps,
846 uint32_t const* starts,
847 uint32_t count,
848 uint32_t word,
849 uint32_t minimum_matches,
850 uint32_t passed,
851 uint32_t (&counters)[CounterBits]
852) {
853 constexpr uint32_t batch = 8U;
854 for (uint32_t i = 0U; i < count && passed != ~0U; i += batch) {
855 // Keep independent bitmap reads in flight before consuming their values.
856 uint32_t loaded[batch]{};
857 _Pragma("unroll")
858 for (uint32_t j = 0U; j < batch; ++j) {
859 if (i + j < count) {
860 loaded[j] = bitmaps[starts[i + j] + word];
861 }
862 }
863 // Reduce eight bitmaps to four input count planes.
864 auto const sum0 = loaded[0] ^ loaded[1] ^ loaded[2];
865 auto const carry0 = (loaded[0] & loaded[1]) | ((loaded[0] | loaded[1]) & loaded[2]);
866 auto const sum1 = loaded[3] ^ loaded[4] ^ loaded[5];
867 auto const carry1 = (loaded[3] & loaded[4]) | ((loaded[3] | loaded[4]) & loaded[5]);
868 auto const ones0 = sum0 ^ sum1 ^ loaded[6];
869 auto const carry2 = (sum0 & sum1) | ((sum0 | sum1) & loaded[6]);
870 auto const twos0 = carry0 ^ carry1 ^ carry2;
871 auto const fours0 = (carry0 & carry1) | ((carry0 | carry1) & carry2);
872 auto const carry3 = ones0 & loaded[7];
873 auto const carry4 = twos0 & carry3;
874 uint32_t const additions[]{
875 ones0 ^ loaded[7], twos0 ^ carry3, fours0 ^ carry4, fours0 & carry4
876 };
877 uint32_t carry = 0U;
878 uint32_t at_least = ~0U;
879 _Pragma("unroll")
880 for (uint32_t bit = 0U; bit < CounterBits; ++bit) {
881 auto const extra = bit < 4U ? additions[bit] : 0U;
882 auto const previous = counters[bit];
883 counters[bit] = previous ^ carry ^ extra;
884 carry = (previous & carry) | ((previous | carry) & extra);
885 auto const key = 0U - ((minimum_matches >> bit) & 1U);
886 at_least = (counters[bit] & ~key) | ((counters[bit] | ~key) & at_least);
887 }
888 // Overflow is above every threshold representable by these counter planes.
889 passed |= at_least | carry;
890 _Pragma("unroll")
891 for (uint32_t bit = CounterBits; bit < 4U; ++bit) {
892 passed |= additions[bit];
893 }
894 }
895 return passed;
896}
897
913template <size_t BucketCount, uint32_t CounterBits, typename QueryRow>
914__global__ __maxnreg__(48) void count_batch_index_tile_kernel(
915 QueryRow const* queries,
916 size_t query_row_offset,
917 uint32_t const* offsets,
918 uint32_t const* postings,
919 uint32_t reference_count,
920 uint32_t indexed_bucket_count,
921 uint16_t key_mask,
922 uint32_t tile,
923 uint32_t* match_counts,
924 uint16_t const* sorted_keys,
925 uint2 const* ranges,
929 uint32_t* candidate_bits,
930 uint32_t const* bitmaps,
931 uint32_t const* bitmap_starts,
932 uint32_t const* bitmap_counts
933) {
934 constexpr uint32_t warp_width = 32;
935 constexpr uint32_t warps_per_block = index_tile_block_size / warp_width;
936 extern __shared__ uint32_t packed_counts[];
937 auto const tiles = (reference_count + tile - 1U) / tile;
938 auto const query_index = static_cast<uint32_t>(blockIdx.x) / tiles;
939 auto low = static_cast<uint32_t>(blockIdx.x) % tiles * tile;
940 auto const high = min(low + tile, reference_count);
941 // All-to-all tiles below the diagonal contain no selected pairs.
942 if (candidate_bits != nullptr && all_to_all && high <= query_id_offset + query_index + 1U) {
943 auto* bits = candidate_bits +
944 static_cast<size_t>(query_index) * candidate_bit_words(reference_count) +
945 low / warp_width;
946 for (auto i = threadIdx.x; i < (high - low + warp_width - 1U) / warp_width;
947 i += blockDim.x) {
948 bits[i] = 0U;
949 }
950 return;
951 }
952 if (candidate_bits != nullptr && all_to_all) {
953 // Keep counters aligned while excluding complete words below the diagonal.
954 auto const first = max(low, (query_id_offset + query_index + 1U) / 64U * 64U);
955 auto* bits = candidate_bits +
956 static_cast<size_t>(query_index) * candidate_bit_words(reference_count) +
957 low / warp_width;
958 for (auto i = threadIdx.x; i < (first - low) / warp_width; i += blockDim.x) {
959 bits[i] = 0U;
960 }
961 low = first;
962 }
963 auto const width = high - low;
965 __shared__ uint32_t long_count;
966 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
967 auto const warp = static_cast<uint32_t>(threadIdx.x) / warp_width;
968 if constexpr (CounterBits != 0U) {
969 // Each lane sums one word's bitmaps; half warps then expand two words at a time, so the
970 // counter stores stay free of bank conflicts.
971 auto const tile_words = (width + warp_width - 1U) / warp_width;
972 auto const count = bitmap_counts[query_index];
973 auto const* starts =
974 bitmap_starts + static_cast<size_t>(query_index) * indexed_bucket_count;
975 for (auto base = warp * warp_width; base < tile_words; base += blockDim.x) {
976 auto const word = low / warp_width + base + lane;
977 uint32_t counters[CounterBits]{};
978 uint32_t passed = ~0U;
979 if (base + lane < tile_words) {
980 // References on or below the diagonal count as passed so that summing can stop
981 // early; the flush excludes them.
982 uint32_t excluded = 0U;
983 if (all_to_all && word * warp_width <= query_id_offset + query_index) {
984 auto const skipped = query_id_offset + query_index - word * warp_width + 1U;
985 excluded = skipped >= warp_width ? ~0U : (1U << skipped) - 1U;
986 }
987 passed = sum_posting_bitmaps(
988 bitmaps,
989 starts,
990 count,
991 word,
993 minimum_matches == 0U ? ~0U : excluded,
994 counters
995 );
996 }
997 auto const shift = (lane % 16U) * 2U;
998 for (uint32_t pair = 0U; pair < warp_width / 2U; ++pair) {
999 auto const source = 2U * pair + lane / 16U;
1000 uint32_t packed = 0U;
1001 _Pragma("unroll")
1002 for (uint32_t plane = 0U; plane < CounterBits; ++plane) {
1003 auto const bits =
1004 (__shfl_sync(0xffffffffU, counters[plane], source) >> shift) & 3U;
1005 packed |= ((bits & 1U) | ((bits & 2U) << 15U)) << plane;
1006 }
1007 auto const pass = __shfl_sync(0xffffffffU, passed, source) >> shift;
1008 if (pass & 1U) {
1009 packed = (packed & 0xffff0000U) | minimum_matches;
1010 }
1011 if (pass & 2U) {
1012 packed = (packed & 0xffffU) | (minimum_matches << 16U);
1013 }
1014 auto const slot = (base + source) * 16U + lane % 16U;
1015 if (slot < (width + 1U) / 2U) {
1016 packed_counts[slot] = packed;
1017 }
1018 }
1019 }
1020 } else {
1021 for (auto i = threadIdx.x; i < (width + 1U) / 2U; i += blockDim.x) {
1022 packed_counts[i] = 0U;
1023 }
1024 }
1025 if (threadIdx.x == 0U) {
1026 long_count = 0U;
1027 }
1028 __syncthreads();
1029 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
1030 auto const* query = queries + (query_row_offset + query_index) * BucketCount;
1031 auto const count = [&](uint32_t reference) {
1032 auto const slot = reference - low;
1033 atomicAdd(&packed_counts[slot / 2U], 1U << ((slot % 2U) * 16U));
1034 };
1035 // Each lane resolves one bucket's posting range, so a warp keeps 32 independent offset
1036 // loads and binary searches in flight; the warp then walks the ranges together.
1037 for (auto base = warp * warp_width; base < indexed_bucket_count;
1038 base += warps_per_block * warp_width) {
1039 auto const bucket = base + lane;
1040 auto const score =
1041 bucket < indexed_bucket_count ? static_cast<uint16_t>(query[bucket]) : 0U;
1042 uint32_t begin = 0U;
1043 uint32_t end = 0U;
1044 if (score != 0U) {
1045 auto const range =
1046 ranges != nullptr
1047 ? ranges[static_cast<size_t>(query_index) * indexed_bucket_count + bucket]
1049 offsets,
1052 bucket,
1053 static_cast<uint32_t>(score & key_mask),
1054 key_count
1055 );
1056 begin = range.x;
1057 end = range.y;
1058 // Long lists are narrowed to the tile by binary search; short ones are cheaper to
1059 // filter while walking them below.
1060 if (end - begin > index_tile_lane_postings) {
1061 if (low != 0U) {
1062 begin = static_cast<uint32_t>(
1063 cuda::std::lower_bound(postings + begin, postings + end, low) - postings
1064 );
1065 }
1066 if (high != reference_count) {
1067 end = static_cast<uint32_t>(
1068 cuda::std::lower_bound(postings + begin, postings + end, high) - postings
1069 );
1070 }
1071 }
1072 }
1073 // Typical ranges hold a few postings, so each lane walks its own short range, keeping
1074 // the postings inside the tile; only long ranges are walked by the whole warp, which
1075 // would otherwise leave most lanes idle.
1076 auto const lane_walk = end - begin <= index_tile_lane_postings;
1077 if (lane_walk) {
1078 auto posting = begin;
1079 // Preload full groups before issuing dependent shared-memory atomics.
1080 for (; end - posting >= 4U; posting += 4U) {
1081 uint32_t ids[4];
1082 _Pragma("unroll")
1083 for (uint32_t j = 0U; j < 4U; ++j) {
1084 ids[j] = postings[posting + j];
1085 }
1086 _Pragma("unroll")
1087 for (uint32_t j = 0U; j < 4U; ++j) {
1088 if (ids[j] >= low && ids[j] < high) {
1089 count(ids[j]);
1090 }
1091 }
1092 if (ids[3] >= high) {
1093 posting = end;
1094 break;
1095 }
1096 }
1097 for (; posting < end; ++posting) {
1098 auto const reference = postings[posting];
1099 if (reference >= high) {
1100 break;
1101 }
1102 if (reference >= low) {
1103 count(reference);
1104 }
1105 }
1106 }
1107 // Only ranges large enough to fill the block are queued; the owning warp walks smaller
1108 // ranges and any that do not fit in the queue.
1109 uint32_t slot = index_tile_long_capacity;
1110 if (end - begin >= index_tile_block_size) {
1111 slot = atomicAdd(&long_count, 1U);
1112 if (slot < index_tile_long_capacity) {
1113 long_ranges[slot] = make_uint2(begin, end);
1114 }
1115 }
1116 auto pending = __ballot_sync(0xffffffffU, !lane_walk && slot >= index_tile_long_capacity);
1117 while (pending != 0U) {
1118 auto const source = static_cast<uint32_t>(__ffs(pending) - 1);
1119 pending &= pending - 1U;
1120 auto const range_end = __shfl_sync(0xffffffffU, end, source);
1121 auto posting = __shfl_sync(0xffffffffU, begin, source) + lane;
1122 for (; posting + 3U * warp_width < range_end; posting += 4U * warp_width) {
1123 uint32_t ids[4];
1124 _Pragma("unroll")
1125 for (uint32_t j = 0U; j < 4U; ++j) {
1126 ids[j] = postings[posting + j * warp_width];
1127 }
1128 _Pragma("unroll")
1129 for (uint32_t j = 0U; j < 4U; ++j) {
1130 count(ids[j]);
1131 }
1132 }
1133 // The four-ID bulk loop leaves at most three independent loads per lane.
1134 auto const remaining = posting < range_end ? range_end - posting : 0U;
1135 uint32_t ids[3];
1136 _Pragma("unroll")
1137 for (uint32_t j = 0U; j < 3U; ++j) {
1138 if (j * warp_width < remaining) {
1139 ids[j] = postings[posting + j * warp_width];
1140 }
1141 }
1142 _Pragma("unroll")
1143 for (uint32_t j = 0U; j < 3U; ++j) {
1144 if (j * warp_width < remaining) {
1145 count(ids[j]);
1146 }
1147 }
1148 }
1149 }
1151 // Every thread takes part in each long range, four loads in flight per thread.
1152 auto const long_ranges_used =
1154 for (uint32_t i = 0U; i < long_ranges_used; ++i) {
1155 auto const range = long_ranges[i];
1156 auto posting = range.x + static_cast<uint32_t>(threadIdx.x);
1157 for (; posting + 3U * index_tile_block_size < range.y;
1158 posting += 4U * index_tile_block_size) {
1159 uint32_t ids[4];
1160 _Pragma("unroll")
1161 for (uint32_t j = 0U; j < 4U; ++j) {
1162 ids[j] = postings[posting + j * index_tile_block_size];
1163 }
1164 _Pragma("unroll")
1165 for (uint32_t j = 0U; j < 4U; ++j) {
1166 count(ids[j]);
1167 }
1168 }
1169 for (; posting < range.y; posting += index_tile_block_size) {
1170 count(postings[posting]);
1171 }
1172 }
1173 __syncthreads();
1174 if (candidate_bits == nullptr) {
1175 auto* const counts =
1176 match_counts + static_cast<size_t>(query_index) * reference_count + low;
1177 for (auto i = threadIdx.x; i < width; i += blockDim.x) {
1178 counts[i] = (packed_counts[i / 2U] >> ((i % 2U) * 16U)) & 0xffffU;
1179 }
1180 return;
1181 }
1182 // Apply the selection predicate here and flush one bit per pair; tiles start on 64-reference
1183 // boundaries, so every word belongs to this block.
1184 auto* const bits = candidate_bits +
1185 static_cast<size_t>(query_index) * candidate_bit_words(reference_count) +
1186 low / 32U;
1187 for (auto base = warp * warp_width; base < width; base += index_tile_block_size) {
1188 auto const i = base + lane;
1189 auto selected = false;
1190 if (i < width) {
1191 auto const count = (packed_counts[i / 2U] >> ((i % 2U) * 16U)) & 0xffffU;
1192 selected = count >= minimum_matches &&
1194 }
1195 auto const word = __ballot_sync(0xffffffffU, selected);
1196 if (lane == 0U) {
1197 bits[base / warp_width] = word;
1198 }
1199 }
1200}
1201
1206template <size_t BucketCount, typename QueryRow>
1207__global__ __launch_bounds__(block_size) void sparse_batch_posting_ranges_kernel(
1208 QueryRow const* queries,
1209 size_t query_row_offset,
1210 uint32_t query_count,
1211 uint16_t const* sorted_keys,
1212 uint32_t const* directory,
1213 uint32_t reference_count,
1214 uint32_t indexed_bucket_count,
1215 uint16_t key_mask,
1216 uint2* ranges
1217) {
1218 auto const bucket = static_cast<uint32_t>(blockIdx.x);
1219 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
1220 for (auto query = static_cast<uint32_t>(threadIdx.x); query < query_count;
1221 query += block_size) {
1222 auto const score =
1223 static_cast<uint16_t>(queries[(query_row_offset + query) * BucketCount + bucket]);
1224 uint2 range{0U, 0U};
1225 if (score != 0U) {
1228 directory,
1230 bucket,
1231 static_cast<uint32_t>(score & key_mask) + (key_count == 32768U ? 1U : 0U)
1232 );
1233 }
1234 ranges[static_cast<size_t>(query) * indexed_bucket_count + bucket] = range;
1235 }
1236}
1237
1240template <typename ReferenceScore>
1241__device__ __forceinline__ void classify_query_words(
1242 pairwise_counts& target,
1243 uint32_t const (&q)[8],
1244 ReferenceScore const* reference
1245) noexcept {
1246 auto const* qs = reinterpret_cast<uint16_t const*>(q);
1247 if constexpr (sizeof(ReferenceScore) == 2U) {
1248 uint32_t r[8];
1249 load_256_global_nc(reinterpret_cast<uint32_t const*>(reference), r);
1250 // Differences of zero-extended 16-bit scores carry the comparison in their sign bit,
1251 // and a sum of two scores minus one is negative only when both are empty. Equal is
1252 // whatever remains of the chunk's 16 buckets.
1253 uint32_t lower = 0U;
1254 uint32_t higher = 0U;
1255 uint32_t empty = 0U;
1256 _Pragma("unroll")
1257 for (uint32_t i = 0; i < 8U; ++i) {
1258 auto const q_low = q[i] & 0xffffU;
1259 auto const q_high = q[i] >> 16U;
1260 auto const r_low = r[i] & 0xffffU;
1261 auto const r_high = r[i] >> 16U;
1262 lower += ((q_low - r_low) >> 31U) + ((q_high - r_high) >> 31U);
1263 higher += ((r_low - q_low) >> 31U) + ((r_high - q_high) >> 31U);
1264 empty += ((q_low + r_low - 1U) >> 31U) + ((q_high + r_high - 1U) >> 31U);
1265 }
1266 target.lower += lower;
1267 target.higher += higher;
1268 target.both_empty += empty;
1269 target.equal += 16U - lower - higher - empty;
1270 } else {
1271 uint32_t r0[8];
1272 uint32_t r1[8];
1273 load_256_global_nc(reinterpret_cast<uint32_t const*>(reference), r0);
1274 load_256_global_nc(reinterpret_cast<uint32_t const*>(reference) + 8U, r1);
1275 _Pragma("unroll")
1276 for (uint32_t i = 0; i < 8U; ++i) {
1277 classify(target, qs[i], r0[i]);
1278 }
1279 _Pragma("unroll")
1280 for (uint32_t i = 0; i < 8U; ++i) {
1281 classify(target, qs[8U + i], r1[i]);
1282 }
1283 }
1284}
1285
1289template <typename SearchResult>
1290__device__ void refine_write_result(
1291 uint32_t index,
1292 uint32_t query_index,
1293 uint32_t reference_id,
1294 uint32_t query_id_offset,
1295 uint32_t const* match_counts,
1296 uint32_t pair_id,
1298 uint32_t lane,
1299 SearchResult* results,
1300 uint32_t* result_match_counts
1301) {
1302 pairwise_counts const total{
1303 .lower = __reduce_add_sync(0xffffffffU, local.lower),
1304 .equal = __reduce_add_sync(0xffffffffU, local.equal),
1305 .higher = __reduce_add_sync(0xffffffffU, local.higher),
1306 .both_empty = __reduce_add_sync(0xffffffffU, local.both_empty),
1307 };
1308 if (lane == 0U) {
1309 results[index].query_id = query_id_offset + query_index;
1310 results[index].reference_id = reference_id;
1311 results[index].counts = total;
1312 if (result_match_counts != nullptr) {
1313 result_match_counts[index] = match_counts[pair_id];
1314 }
1315 }
1316}
1317
1323constexpr size_t refine_block_row_bytes = size_t{8} << 20U;
1324
1326constexpr uint32_t score_planes = 16U;
1327
1334__host__ __device__ constexpr uint32_t score_plane_index(uint32_t group, uint32_t plane) noexcept {
1335 return (((group / 32U) * 4U + plane / 4U) * 32U + group % 32U) * 4U + plane % 4U;
1336}
1337
1339__device__ __forceinline__ void
1340write_score_planes(uint32_t score, uint32_t group, uint32_t lane, uint32_t* planes) {
1341 uint32_t mine = 0U;
1342 _Pragma("unroll")
1343 for (uint32_t plane = 0; plane < score_planes; ++plane) {
1344 auto const word = __ballot_sync(0xffffffffU, ((score >> plane) & 1U) != 0U);
1345 if (lane == plane) {
1346 mine = word;
1347 }
1348 }
1349 if (lane < score_planes) {
1351 }
1352}
1353
1355template <size_t BucketCount>
1356__global__ __launch_bounds__(
1358) void build_score_planes_kernel(uint16_t const* rows, uint32_t row_count, uint32_t* planes) {
1359 constexpr uint32_t warp_width = 32;
1360 constexpr uint32_t groups_per_row = static_cast<uint32_t>(BucketCount / warp_width);
1361 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
1362 auto const total = static_cast<size_t>(row_count) * groups_per_row;
1363 auto const stride = static_cast<size_t>(gridDim.x) * (block_size / warp_width);
1364 for (auto item = (static_cast<size_t>(blockIdx.x) * block_size + threadIdx.x) / warp_width;
1365 item < total;
1366 item += stride) {
1367 auto const row = item / groups_per_row;
1368 auto const group = static_cast<uint32_t>(item % groups_per_row);
1370 rows[row * BucketCount + group * warp_width + lane],
1371 group,
1372 lane,
1373 planes + row * (BucketCount / 2U)
1374 );
1375 }
1376}
1377
1379__device__ __forceinline__ void
1380load_plane_group(uint4 const* row, uint32_t group, uint32_t (&words)[score_planes]) {
1381 _Pragma("unroll")
1382 for (uint32_t c = 0; c < score_planes / 4U; ++c) {
1383 auto const v = row[((group / 32U) * 4U + c) * 32U + group % 32U];
1384 words[4U * c] = v.x;
1385 words[4U * c + 1U] = v.y;
1386 words[4U * c + 2U] = v.z;
1387 words[4U * c + 3U] = v.w;
1388 }
1389}
1390
1396 uint32_t lower{};
1398 uint32_t differ{};
1400 uint32_t occupied{};
1401 uint32_t matches{};
1402
1404 template <size_t BucketCount, bool ReferenceFull = false>
1405 [[nodiscard]] __device__ pairwise_counts reduce() const noexcept {
1406 auto const total_lower = __reduce_add_sync(0xffffffffU, lower);
1407 auto const total_differ = __reduce_add_sync(0xffffffffU, differ);
1408 auto const total_occupied = ReferenceFull ? static_cast<uint32_t>(BucketCount)
1409 : __reduce_add_sync(0xffffffffU, occupied);
1410 return {
1411 .lower = total_lower,
1412 .equal = total_occupied - total_differ,
1413 .higher = total_differ - total_lower,
1414 .both_empty = static_cast<uint32_t>(BucketCount) - total_occupied,
1415 };
1416 }
1417};
1418
1420[[nodiscard]] __device__ __forceinline__ uint32_t
1421plane_any(uint32_t const (&planes)[score_planes]) noexcept {
1422 uint32_t any = 0U;
1423 _Pragma("unroll")
1424 for (uint32_t p = 0; p < score_planes; ++p) {
1425 any |= planes[p];
1426 }
1427 return any;
1428}
1429
1431[[nodiscard]] __device__ __forceinline__ uint32_t
1432plane_low_any(uint32_t const (&planes)[score_planes]) noexcept {
1433 uint32_t any = 0U;
1434 _Pragma("unroll")
1435 for (uint32_t p = 0; p + 1U < score_planes; ++p) {
1436 any |= planes[p];
1437 }
1438 return any;
1439}
1440
1456template <bool CountMatches, bool ReferenceFull = false>
1457__device__ __forceinline__ void compare_plane_group(
1458 uint32_t const (&q)[score_planes],
1459 uint32_t const (&r)[score_planes],
1460 uint32_t reference_any,
1461 uint32_t reference_low_any,
1462 uint16_t key_mask,
1463 uint32_t match_lanes,
1465) noexcept {
1466 uint32_t lt = 0U;
1467 uint32_t ne = 0U;
1468 uint32_t low_ne = 0U;
1469 _Pragma("unroll")
1470 for (uint32_t p = 0; p < score_planes; ++p) {
1471 if (p == score_planes - 1U) {
1472 low_ne = ne;
1473 }
1474 lt = (~q[p] & r[p]) | (~(q[p] ^ r[p]) & lt);
1475 ne |= q[p] ^ r[p];
1476 }
1477 counts.lower += static_cast<uint32_t>(__popc(lt));
1478 counts.differ += static_cast<uint32_t>(__popc(ne));
1479 if constexpr (!ReferenceFull) {
1480 counts.occupied += static_cast<uint32_t>(__popc(ne | reference_any));
1481 }
1482 if constexpr (CountMatches) {
1483 // Validated key masks cover the low planes and, for a full key, the top one. Where the
1484 // compared planes agree, the low planes agree, so both rows are nonzero exactly when the
1485 // shared low planes are or both top bits are set.
1486 constexpr auto top = score_planes - 1U;
1487 auto const top_keyed = 0U - (static_cast<uint32_t>(key_mask) >> top);
1488 auto const key_differs = (ne & top_keyed) | (low_ne & ~top_keyed);
1489 counts.matches += static_cast<uint32_t>(
1490 __popc(~key_differs & (reference_low_any | (q[top] & r[top])) & match_lanes)
1491 );
1492 }
1493}
1494
1496template <size_t BucketCount>
1497__global__ __launch_bounds__(
1499) void decode_score_planes_kernel(uint32_t const* planes, size_t row_count, uint16_t* rows) {
1500 constexpr uint32_t warp_width = 32;
1501 constexpr uint32_t groups_per_row = static_cast<uint32_t>(BucketCount / warp_width);
1502 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
1503 auto const total = row_count * groups_per_row;
1504 auto const stride = static_cast<size_t>(gridDim.x) * (block_size / warp_width);
1505 for (auto item = (static_cast<size_t>(blockIdx.x) * block_size + threadIdx.x) / warp_width;
1506 item < total;
1507 item += stride) {
1508 auto const row = item / groups_per_row;
1509 auto const group = static_cast<uint32_t>(item % groups_per_row);
1510 uint32_t words[score_planes];
1512 reinterpret_cast<uint4 const*>(planes + row * (BucketCount / 2U)), group, words
1513 );
1514 uint32_t score = 0U;
1515 _Pragma("unroll")
1516 for (uint32_t p = 0; p < score_planes; ++p) {
1517 score |= ((words[p] >> lane) & 1U) << p;
1518 }
1519 rows[row * BucketCount + group * warp_width + lane] = static_cast<uint16_t>(score);
1520 }
1521}
1522
1524[[nodiscard]] inline uint32_t warp_grid_blocks(size_t warps) noexcept {
1525 constexpr size_t warps_per_block = block_size / 32U;
1526 return static_cast<uint32_t>(
1527 cuda::std::min<size_t>((warps + warps_per_block - 1U) / warps_per_block, 65535U)
1528 );
1529}
1530
1536template <size_t BucketCount, typename SearchResult>
1537__global__ __launch_bounds__(block_size) void single_query_planes_kernel(
1538 uint32_t const* query_planes,
1539 uint32_t const* reference_planes,
1540 uint32_t reference_count,
1541 uint32_t const* candidate_ids,
1542 uint32_t const* candidate_count,
1543 SearchResult* results
1544) {
1545 constexpr uint32_t warp_width = 32;
1546 constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U);
1547 constexpr uint32_t groups_per_row = static_cast<uint32_t>(BucketCount / warp_width);
1548 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
1550 auto const stride = static_cast<uint32_t>(gridDim.x) * (block_size / warp_width);
1551 auto const* query = reinterpret_cast<uint4 const*>(query_planes);
1552 for (auto item = (static_cast<uint32_t>(blockIdx.x) * block_size + threadIdx.x) / warp_width;
1553 item < items;
1554 item += stride) {
1555 auto const reference_id = candidate_ids == nullptr ? item : candidate_ids[item];
1556 auto const* reference = reinterpret_cast<uint4 const*>(
1557 reference_planes + static_cast<size_t>(reference_id) * row_words
1558 );
1560 for (auto group = lane; group < groups_per_row; group += warp_width) {
1561 uint32_t q[score_planes];
1562 uint32_t r[score_planes];
1564 load_plane_group(reference, group, r);
1565 compare_plane_group<false>(q, r, plane_any(r), 0U, 0U, 0U, counts);
1566 }
1567 auto const total = counts.reduce<BucketCount>();
1568 if (lane == 0U) {
1569 results[item].reference_id = reference_id;
1570 results[item].counts = total;
1571 }
1572 }
1573}
1574
1579template <size_t BucketCount>
1580__global__ __launch_bounds__(block_size) void count_plane_matches_kernel(
1581 uint32_t const* query_planes,
1582 uint32_t query_count,
1583 uint32_t const* reference_planes,
1584 uint32_t reference_count,
1585 uint32_t indexed_bucket_count,
1586 uint16_t key_mask,
1587 uint32_t* match_counts
1588) {
1589 constexpr uint32_t warp_width = 32;
1590 constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U);
1591 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
1592 auto const pairs = static_cast<uint64_t>(query_count) * reference_count;
1593 auto const stride = static_cast<uint64_t>(gridDim.x) * (block_size / warp_width);
1594 auto const indexed_groups = indexed_bucket_count / warp_width;
1595 for (auto pair = (static_cast<uint64_t>(blockIdx.x) * block_size + threadIdx.x) / warp_width;
1596 pair < pairs;
1597 pair += stride) {
1598 auto const query_index = pair / reference_count;
1599 auto const reference_id = pair % reference_count;
1600 auto const* query = reinterpret_cast<uint4 const*>(query_planes + query_index * row_words);
1601 auto const* reference =
1602 reinterpret_cast<uint4 const*>(reference_planes + reference_id * row_words);
1604 for (auto group = lane; group < indexed_groups; group += warp_width) {
1605 uint32_t q[score_planes];
1606 uint32_t r[score_planes];
1608 load_plane_group(reference, group, r);
1609 compare_plane_group<true>(q, r, plane_any(r), plane_low_any(r), key_mask, ~0U, counts);
1610 }
1611 auto const total = __reduce_add_sync(0xffffffffU, counts.matches);
1612 if (lane == 0U) {
1613 match_counts[pair] = total;
1614 }
1615 }
1616}
1617
1619static __global__ __launch_bounds__(block_size) void candidate_bits_from_counts_kernel(
1620 uint32_t const* match_counts,
1621 uint32_t query_count,
1622 uint32_t reference_count,
1623 uint32_t minimum_matches,
1624 uint32_t query_id_offset,
1625 bool all_to_all,
1626 uint32_t* candidate_bits
1627) {
1628 auto const words = candidate_bit_words(reference_count);
1629 auto const total = static_cast<size_t>(query_count) * words;
1630 for (auto item = static_cast<size_t>(blockIdx.x) * block_size + threadIdx.x; item < total;
1631 item += static_cast<size_t>(gridDim.x) * block_size) {
1632 auto const query_index = static_cast<uint32_t>(item / words);
1633 auto const first = static_cast<uint32_t>(item % words) * 32U;
1634 uint32_t bits = 0U;
1635 for (uint32_t bit = 0U; bit < 32U && first + bit < reference_count; ++bit) {
1636 auto const reference_id = first + bit;
1637 auto const selected =
1638 match_counts[static_cast<size_t>(query_index) * reference_count + reference_id] >=
1640 (!all_to_all || query_id_offset + query_index < reference_id);
1641 bits |= static_cast<uint32_t>(selected) << bit;
1642 }
1643 candidate_bits[item] = bits;
1644 }
1645}
1646
1649constexpr uint32_t bitmap_refine_block_size = 768U;
1650
1655constexpr uint32_t bitmap_refine_waves = 8U;
1656
1659constexpr size_t bitmap_refine_shared_bytes = 96U * 1024U;
1660
1662template <size_t BucketCount>
1663constexpr bool bitmap_refine_staged = bitmap_refine_shared_bytes >= BucketCount * sizeof(uint16_t);
1664
1669template <size_t BucketCount>
1670constexpr uint32_t bitmap_refine_group =
1671 bitmap_refine_staged<BucketCount>
1672 ? static_cast<uint32_t>(cuda::std::min<size_t>(
1673 24U,
1674 bitmap_refine_shared_bytes / (BucketCount * sizeof(uint16_t))
1675 ))
1676 : 8U;
1677
1679template <size_t BucketCount>
1681 bitmap_refine_staged<BucketCount>
1682 ? static_cast<size_t>(bitmap_refine_group<BucketCount>) * BucketCount * sizeof(uint16_t)
1683 : 0U;
1684
1689__device__ __forceinline__ uint32_t
1690next_cell(uint32_t* cell_counter, uint32_t cell, uint32_t& claimed) {
1691 if (cell_counter == nullptr) {
1692 return cell + gridDim.x;
1693 }
1694 __syncthreads();
1695 if (threadIdx.x == 0U) {
1696 claimed = atomicAdd(cell_counter, 1U);
1697 }
1698 __syncthreads();
1699 return claimed;
1700}
1701
1705 uint32_t groups;
1706 uint32_t first_word;
1707 uint32_t block_words;
1708 uint32_t cell_count;
1709};
1710
1716template <bool UpperTriangle>
1717__device__ __forceinline__ refine_cells make_refine_cells(
1718 uint32_t query_id_offset,
1719 uint32_t query_count,
1720 uint32_t group,
1721 uint32_t words_per_query,
1722 uint32_t max_block_words,
1723 uint32_t waves
1724) {
1725 auto const groups = (query_count + group - 1U) / group;
1726 auto const first_word =
1727 UpperTriangle ? cuda::std::min(query_id_offset / 32U, words_per_query) : 0U;
1728 auto const words = words_per_query - first_word;
1729 auto const blocks_per_group = cuda::std::max(1U, waves * gridDim.x / groups);
1730 auto const block_words = cuda::std::max(
1731 1U, cuda::std::min(max_block_words, (words + blocks_per_group - 1U) / blocks_per_group)
1732 );
1733 return {groups, first_word, block_words, groups * ((words + block_words - 1U) / block_words)};
1734}
1735
1739 bitmap,
1741 all,
1744};
1745
1750template <bool UpperTriangle>
1751[[nodiscard]] __host__ __device__ inline uint64_t batch_result_slot(
1752 uint32_t query_index,
1753 uint32_t reference_id,
1754 uint32_t query_id_offset,
1755 uint32_t reference_count
1756) noexcept {
1757 if constexpr (UpperTriangle) {
1758 auto const query = static_cast<uint64_t>(query_index);
1759 auto const preceding = query * (reference_count - query_id_offset - 1U) -
1760 query * (query == 0U ? 0U : query - 1U) / 2U;
1761 return preceding + reference_id - (query_id_offset + query_index) - 1U;
1762 } else {
1763 return static_cast<uint64_t>(query_index) * reference_count + reference_id;
1764 }
1765}
1766
1769 __host__ __device__ uint32_t operator()(uint32_t word) const noexcept {
1770 return static_cast<uint32_t>(cuda::std::popcount(word));
1771 }
1772};
1773
1780template <bool UpperTriangle, typename SearchResult>
1781__global__ __launch_bounds__(block_size) void gather_passing_kernel(
1782 SearchResult const* results,
1783 uint32_t const* match_counts,
1784 uint32_t const* pass_bits,
1785 uint32_t const* word_offsets,
1787 uint32_t reference_count,
1789 size_t word_end,
1790 uint32_t packed_base,
1791 SearchResult* packed,
1793) {
1794 constexpr uint32_t warp_width = 32;
1795 auto const words_per_query = candidate_bit_words(reference_count);
1796 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
1797 for (auto w =
1798 word_begin + (static_cast<size_t>(blockIdx.x) * block_size + threadIdx.x) / warp_width;
1799 w < word_end;
1800 w += static_cast<size_t>(gridDim.x) * (block_size / warp_width)) {
1801 auto const bits = pass_bits[w];
1802 if (((bits >> lane) & 1U) == 0U) {
1803 continue;
1804 }
1805 auto const query = static_cast<uint32_t>(w / words_per_query);
1806 auto const reference = static_cast<uint32_t>(w % words_per_query) * warp_width + lane;
1807 auto const slot =
1808 batch_result_slot<UpperTriangle>(query, reference, first_query_id, reference_count);
1809 auto const position = word_offsets[w] - packed_base +
1810 static_cast<uint32_t>(__popc(bits & ((1U << lane) - 1U)));
1811 packed[position] = results[slot];
1812 if (packed_match_counts != nullptr) {
1813 packed_match_counts[position] = match_counts[slot];
1814 }
1815 }
1816}
1817
1835template <
1836 size_t BucketCount,
1837 bool CountMatches,
1838 typename SearchResult,
1839 refine_candidates Candidates,
1840 bool UpperTriangle,
1841 uint32_t QueryGroup>
1842__global__ __launch_bounds__(bitmap_refine_block_size) void refine_batch_bitmap_kernel(
1843 uint32_t const* query_planes,
1844 uint32_t query_id_offset,
1845 uint32_t query_count,
1846 uint32_t const* reference_planes,
1847 uint32_t reference_count,
1848 uint32_t indexed_bucket_count,
1849 uint16_t key_mask,
1850 uint32_t const* candidate_bits,
1851 SearchResult* results,
1853 uint32_t minimum_matches,
1854 uint32_t* pass_bits,
1856) {
1857 constexpr uint32_t warp_width = 32;
1858 constexpr uint32_t group = QueryGroup;
1859 static_assert(group >= 1U && group <= warp_width);
1860 constexpr bool staged = bitmap_refine_staged<BucketCount>;
1861 constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U);
1862 constexpr uint32_t groups_per_row = static_cast<uint32_t>(BucketCount / warp_width);
1863 static_assert(groups_per_row % warp_width == 0U);
1864 constexpr uint32_t lane_groups = groups_per_row / warp_width;
1865 // Reference groups one lane keeps in registers across the selecting queries.
1866 constexpr uint32_t cached_groups = lane_groups < 2U ? lane_groups : 2U;
1867 constexpr uint32_t max_block_words = static_cast<uint32_t>(cuda::std::max<size_t>(
1868 1U, refine_block_row_bytes / (BucketCount * sizeof(uint16_t)) / warp_width
1869 ));
1870 // Bitmap selections cluster in a few words, so their warps claim quarter words.
1871 constexpr uint32_t word_slices = Candidates == refine_candidates::all ? 1U : 4U;
1872 extern __shared__ uint4 query_plane_storage[];
1873 __shared__ uint32_t next_word;
1874 __shared__ uint32_t claimed_cell;
1875
1876 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
1877 auto const words_per_query = candidate_bit_words(reference_count);
1878 auto const cells = make_refine_cells<UpperTriangle>(
1881 group,
1885 );
1886 auto const groups = cells.groups;
1887 auto const indexed_groups = indexed_bucket_count / warp_width;
1888 auto const thresholded = CountMatches && pass_bits != nullptr;
1889
1890 for (auto cell = next_cell(cell_counter, blockIdx.x - gridDim.x, claimed_cell);
1891 cell < cells.cell_count;
1892 cell = next_cell(cell_counter, cell, claimed_cell)) {
1893 auto const first_query = (cell % groups) * group;
1894 auto const group_size = cuda::std::min(group, query_count - first_query);
1895 auto const first_word = cells.first_word + (cell / groups) * cells.block_words;
1896 auto const last_word = cuda::std::min(first_word + cells.block_words, words_per_query);
1897 auto const my_query = lane < group_size ? first_query + lane : first_query;
1898 auto const* group_planes = reinterpret_cast<uint4 const*>(
1899 query_planes + static_cast<size_t>(first_query) * row_words
1900 );
1901
1902 __syncthreads();
1903 if (threadIdx.x == 0U) {
1905 }
1906 if constexpr (staged) {
1907 constexpr uint32_t member_vectors = row_words / 4U;
1908 for (auto i = static_cast<uint32_t>(threadIdx.x); i < group_size * member_vectors;
1910 query_plane_storage[i] = reinterpret_cast<uint4 const*>(
1911 query_planes + static_cast<size_t>(first_query + i / member_vectors) * row_words
1912 )[i % member_vectors];
1913 }
1914 }
1915 __syncthreads();
1916 uint4 const* const member_planes = staged ? query_plane_storage : group_planes;
1917
1918 // Words carry uneven candidate counts, so warps claim them one slice at a time.
1919 for (;;) {
1920 uint32_t claim = 0U;
1921 if (lane == 0U) {
1922 claim = atomicAdd(&next_word, 1U);
1923 }
1924 claim = __shfl_sync(0xffffffffU, claim, 0);
1925 auto const word = claim / word_slices;
1926 if (word >= last_word) {
1927 break;
1928 }
1929 // Lane m holds query m's selected references in this slice of the word.
1930 uint32_t bits = 0U;
1931 if (lane < group_size) {
1932 constexpr uint32_t slice_width = warp_width / word_slices;
1933 constexpr uint32_t slice_mask =
1934 slice_width == warp_width ? ~0U : (1U << slice_width) - 1U;
1935 if constexpr (Candidates == refine_candidates::bitmap) {
1936 bits = candidate_bits[static_cast<size_t>(my_query) * words_per_query + word] &
1937 (slice_mask << (claim % word_slices * slice_width));
1938 } else {
1939 auto const first_reference = word * warp_width;
1940 auto const left = reference_count - first_reference;
1941 bits = left >= warp_width ? ~0U : (1U << left) - 1U;
1942 if constexpr (UpperTriangle) {
1943 auto const query_id = query_id_offset + my_query;
1944 if (first_reference <= query_id) {
1945 auto const skipped = query_id - first_reference + 1U;
1946 bits &= skipped >= warp_width ? 0U : ~0U << skipped;
1947 }
1948 }
1949 if constexpr (Candidates == refine_candidates::reference_mask) {
1951 (slice_mask << (claim % word_slices * slice_width));
1952 }
1953 }
1954 }
1955 // Lane m collects query m's passing references in this word.
1956 uint32_t passed = 0U;
1957 auto remaining = __reduce_or_sync(0xffffffffU, bits);
1958 while (remaining != 0U) {
1959 auto const bit = static_cast<uint32_t>(__ffs(remaining) - 1);
1960 remaining &= remaining - 1U;
1961 auto const reference_id = word * warp_width + bit;
1962 auto const selected = __ballot_sync(0xffffffffU, ((bits >> bit) & 1U) != 0U);
1963 auto const* reference = reinterpret_cast<uint4 const*>(
1964 reference_planes + static_cast<size_t>(reference_id) * row_words
1965 );
1966 uint32_t r[cached_groups][score_planes];
1967 uint32_t r_any[cached_groups];
1968 uint32_t r_low_any[cached_groups];
1969 _Pragma("unroll")
1970 for (uint32_t j = 0; j < cached_groups; ++j) {
1971 load_plane_group(reference, j * warp_width + lane, r[j]);
1972 r_any[j] = plane_any(r[j]);
1973 r_low_any[j] = CountMatches ? plane_low_any(r[j]) : 0U;
1974 }
1975 uint32_t reference_nonzero = ~0U;
1976 _Pragma("unroll")
1977 for (uint32_t j = 0; j < cached_groups; ++j) {
1978 reference_nonzero &= r_any[j];
1979 }
1980 auto const reference_full = cached_groups == lane_groups &&
1981 __all_sync(0xffffffffU, reference_nonzero == ~0U);
1982 // Lane m keeps member m's totals, so the selecting lanes store their records
1983 // together once the reference is done.
1984 uint32_t mine_lower = 0U;
1985 uint32_t mine_equal = 0U;
1986 uint32_t mine_empty = 0U;
1987 uint32_t mine_matches = 0U;
1988 auto const compare_members = [&](auto full) {
1989 auto const compare_member = [&](uint32_t m) {
1990 auto const* member =
1991 member_planes + static_cast<size_t>(m) * (row_words / 4U);
1992 plane_counts counts{};
1993 auto const compare = [&](uint32_t bucket_group,
1994 uint32_t const(&rg)[score_planes],
1995 uint32_t rg_any,
1996 uint32_t rg_low_any) {
1997 uint32_t q[score_planes];
1998 load_plane_group(member, bucket_group, q);
1999 compare_plane_group<CountMatches, decltype(full)::value>(
2000 q,
2001 rg,
2002 rg_any,
2003 rg_low_any,
2004 key_mask,
2005 bucket_group < indexed_groups ? ~0U : 0U,
2006 counts
2007 );
2008 };
2009 _Pragma("unroll")
2010 for (uint32_t j = 0; j < cached_groups; ++j) {
2011 compare(j * warp_width + lane, r[j], r_any[j], r_low_any[j]);
2012 }
2013 for (uint32_t j = cached_groups; j < lane_groups; ++j) {
2014 uint32_t rg[score_planes];
2015 load_plane_group(reference, j * warp_width + lane, rg);
2016 compare(
2017 j * warp_width + lane,
2018 rg,
2019 plane_any(rg),
2020 CountMatches ? plane_low_any(rg) : 0U
2021 );
2022 }
2023 auto const total = counts.reduce<BucketCount, decltype(full)::value>();
2024 uint32_t counts_matches_total = 0U;
2025 if constexpr (CountMatches) {
2026 counts_matches_total = __reduce_add_sync(0xffffffffU, counts.matches);
2027 }
2028 if (lane == m) {
2029 mine_lower = total.lower;
2030 mine_equal = total.equal;
2031 mine_empty = total.both_empty;
2032 mine_matches = counts_matches_total;
2033 }
2034 };
2035 if constexpr (Candidates != refine_candidates::bitmap) {
2036 // Layout and reference-mask selections are prefixes of the query group.
2037 auto const member_count = static_cast<uint32_t>(__popc(selected));
2038 for (uint32_t m = 0U; m < member_count; ++m) {
2039 compare_member(m);
2040 }
2041 } else {
2042 for (auto members = selected; members != 0U; members &= members - 1U) {
2043 compare_member(static_cast<uint32_t>(__ffs(members) - 1));
2044 }
2045 }
2046 };
2047 if (reference_full) {
2048 compare_members(cuda::std::true_type{});
2049 } else {
2050 compare_members(cuda::std::false_type{});
2051 }
2052 auto const keep = !thresholded || mine_matches >= minimum_matches;
2053 if (((selected >> lane) & 1U) != 0U && keep) {
2054 auto const slot = batch_result_slot<UpperTriangle>(
2055 my_query, reference_id, query_id_offset, reference_count
2056 );
2057 // The slot implies the pair; only its counts are stored.
2058 results[slot] = SearchResult::pack(
2059 mine_lower,
2060 mine_equal,
2061 static_cast<uint32_t>(BucketCount) - mine_lower - mine_equal - mine_empty
2062 );
2063 if constexpr (CountMatches) {
2064 if (result_match_counts != nullptr) {
2065 result_match_counts[slot] = mine_matches;
2066 }
2067 }
2068 passed |= 1U << bit;
2069 }
2070 }
2071 if constexpr (Candidates == refine_candidates::bitmap) {
2072 // Slices of a word may run concurrently, so failing pairs are cleared in place.
2073 if (thresholded && (bits & ~passed) != 0U) {
2074 atomicAnd(
2075 &pass_bits[static_cast<size_t>(my_query) * words_per_query + word],
2076 ~(bits & ~passed)
2077 );
2078 }
2079 } else if constexpr (Candidates == refine_candidates::all) {
2080 if (thresholded && lane < group_size) {
2081 pass_bits[static_cast<size_t>(my_query) * words_per_query + word] = passed;
2082 }
2083 }
2084 }
2085 }
2086}
2087
2090constexpr uint32_t match_filter_block_size = 512U;
2091
2094template <size_t BucketCount>
2096 bitmap_refine_staged<BucketCount> && BucketCount % 1024U == 0U && BucketCount <= 2048U;
2097
2100__device__ __forceinline__ void
2101load_plane_chunk(uint4 const* row, uint32_t group, uint32_t chunk, uint32_t (&words)[4]) {
2102 auto const value = row[((group / 32U) * 4U + chunk) * 32U + group % 32U];
2103 words[0] = value.x;
2104 words[1] = value.y;
2105 words[2] = value.z;
2106 words[3] = value.w;
2107}
2108
2110constexpr uint32_t match_filter_planes = 12U;
2111
2124template <size_t BucketCount, bool UpperTriangle, uint32_t QueryGroup>
2126 uint32_t const* query_planes,
2127 uint32_t query_id_offset,
2128 uint32_t query_count,
2129 uint32_t const* reference_planes,
2130 uint32_t reference_count,
2131 uint32_t indexed_bucket_count,
2132 uint32_t minimum_matches,
2133 uint32_t* candidate_bits,
2134 uint32_t* cell_counter
2135) {
2136 static_assert(match_filter_supported<BucketCount>);
2137 constexpr uint32_t warp_width = 32;
2138 constexpr uint32_t group = QueryGroup;
2139 static_assert(group >= 1U && group <= warp_width);
2140 constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U);
2141 constexpr uint32_t lane_groups = static_cast<uint32_t>(BucketCount / warp_width / warp_width);
2142 constexpr uint32_t max_block_words = static_cast<uint32_t>(cuda::std::max<size_t>(
2143 1U, refine_block_row_bytes / (BucketCount * sizeof(uint16_t)) / warp_width
2144 ));
2145 extern __shared__ uint4 filter_query_planes[];
2146 __shared__ uint32_t next_word;
2147 __shared__ uint32_t claimed_cell;
2148
2149 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
2150 auto const words_per_query = candidate_bit_words(reference_count);
2151 auto const cells = make_refine_cells<UpperTriangle>(
2153 );
2154 auto const groups = cells.groups;
2155 auto const indexed_groups = indexed_bucket_count / warp_width;
2156 // Unindexed buckets are seeded as differing, so they never count toward the bound.
2157 uint32_t excluded_lanes[lane_groups];
2158 _Pragma("unroll")
2159 for (uint32_t j = 0; j < lane_groups; ++j) {
2160 excluded_lanes[j] = j * warp_width + lane < indexed_groups ? 0U : ~0U;
2161 }
2162
2163 for (auto cell = next_cell(cell_counter, blockIdx.x - gridDim.x, claimed_cell);
2164 cell < cells.cell_count;
2165 cell = next_cell(cell_counter, cell, claimed_cell)) {
2166 auto const first_query = (cell % groups) * group;
2167 auto const group_size = cuda::std::min(group, query_count - first_query);
2168 auto const first_word = cells.first_word + (cell / groups) * cells.block_words;
2169 auto const last_word = cuda::std::min(first_word + cells.block_words, words_per_query);
2170 auto const* group_planes = reinterpret_cast<uint4 const*>(
2171 query_planes + static_cast<size_t>(first_query) * row_words
2172 );
2173
2174 __syncthreads();
2175 if (threadIdx.x == 0U) {
2177 }
2178 for (auto i = static_cast<uint32_t>(threadIdx.x); i < group_size * (row_words / 4U);
2180 filter_query_planes[i] = group_planes[i];
2181 }
2182 __syncthreads();
2183
2184 for (;;) {
2185 uint32_t word = 0U;
2186 if (lane == 0U) {
2187 word = atomicAdd(&next_word, 1U);
2188 }
2189 word = __shfl_sync(0xffffffffU, word, 0);
2190 if (word >= last_word) {
2191 break;
2192 }
2193 // Lane m holds query m's selected references in this word.
2194 uint32_t bits = 0U;
2195 if (lane < group_size) {
2196 auto const first_reference = word * warp_width;
2197 auto const left = reference_count - first_reference;
2198 bits = left >= warp_width ? ~0U : (1U << left) - 1U;
2199 if constexpr (UpperTriangle) {
2200 auto const query_id = query_id_offset + first_query + lane;
2201 if (first_reference <= query_id) {
2202 auto const skipped = query_id - first_reference + 1U;
2203 bits &= skipped >= warp_width ? 0U : ~0U << skipped;
2204 }
2205 }
2206 }
2207 uint32_t passed = 0U;
2208 auto remaining = __reduce_or_sync(0xffffffffU, bits);
2209 constexpr uint32_t refs = 4U;
2210 constexpr uint32_t bound_chunks = match_filter_planes / 4U;
2211 constexpr uint32_t bound_planes = match_filter_planes;
2212 while (remaining != 0U) {
2213 uint32_t bit[refs];
2214 bool has[refs];
2215 uint32_t last_bit = 0U;
2216 _Pragma("unroll")
2217 for (uint32_t i = 0; i < refs; ++i) {
2218 has[i] = remaining != 0U;
2219 bit[i] = has[i] ? static_cast<uint32_t>(__ffs(remaining) - 1) : bit[0];
2220 last_bit = has[i] ? bit[i] : last_bit;
2221 remaining &= remaining - 1U;
2222 }
2223 uint4 const* reference[refs];
2224 uint32_t selecting[refs];
2225 uint32_t r[refs][lane_groups][bound_planes];
2226 _Pragma("unroll")
2227 for (uint32_t i = 0; i < refs; ++i) {
2228 reference[i] = reinterpret_cast<uint4 const*>(
2230 static_cast<size_t>(word * warp_width + bit[i]) * row_words
2231 );
2232 selecting[i] = has[i] ? __ballot_sync(0xffffffffU, (bits >> bit[i]) & 1U) : 0U;
2233 _Pragma("unroll")
2234 for (uint32_t j = 0; j < lane_groups; ++j) {
2235 _Pragma("unroll")
2236 for (uint32_t c = 0; c < bound_chunks; ++c) {
2237 uint32_t words[4];
2238 load_plane_chunk(reference[i], j * warp_width + lane, c, words);
2239 _Pragma("unroll")
2240 for (uint32_t w = 0; w < 4U; ++w) {
2241 r[i][j][4U * c + w] = words[w];
2242 }
2243 }
2244 }
2245 }
2246 // Selections are prefixes of the query group, growing with the reference ID.
2247 auto const members = static_cast<uint32_t>(
2248 __popc(__ballot_sync(0xffffffffU, (bits >> last_bit) & 1U))
2249 );
2250 for (uint32_t m = 0U; m < members; ++m) {
2251 auto const* member =
2252 filter_query_planes + static_cast<size_t>(m) * (row_words / 4U);
2253 uint32_t mismatches[refs] = {};
2254 _Pragma("unroll")
2255 for (uint32_t j = 0; j < lane_groups; ++j) {
2256 uint32_t differ[refs];
2257 _Pragma("unroll")
2258 for (uint32_t i = 0; i < refs; ++i) {
2259 differ[i] = excluded_lanes[j];
2260 }
2261 _Pragma("unroll")
2262 for (uint32_t c = 0; c < bound_chunks; ++c) {
2263 uint32_t q[4];
2264 load_plane_chunk(member, j * warp_width + lane, c, q);
2265 _Pragma("unroll")
2266 for (uint32_t i = 0; i < refs; ++i) {
2267 _Pragma("unroll")
2268 for (uint32_t w = 0; w < 4U; ++w) {
2269 differ[i] |= q[w] ^ r[i][j][4U * c + w];
2270 }
2271 }
2272 }
2273 _Pragma("unroll")
2274 for (uint32_t i = 0; i < refs; ++i) {
2275 mismatches[i] += static_cast<uint32_t>(__popc(differ[i]));
2276 }
2277 }
2278 _Pragma("unroll")
2279 for (uint32_t i = 0; i < refs; ++i) {
2280 auto const total_bound = static_cast<uint32_t>(BucketCount) -
2281 __reduce_add_sync(0xffffffffU, mismatches[i]);
2282 if (lane == m && ((selecting[i] >> m) & 1U) != 0U &&
2283 total_bound >= minimum_matches) {
2284 passed |= 1U << bit[i];
2285 }
2286 }
2287 }
2288 }
2289 if (lane < group_size) {
2290 candidate_bits[static_cast<size_t>(first_query + lane) * words_per_query + word] =
2291 passed;
2292 }
2293 }
2294 }
2295}
2296
2299constexpr uint32_t all_pairs_block_size = 512U;
2300
2305constexpr uint32_t all_pairs_word_parts = 4U;
2306
2315template <size_t BucketCount, typename SearchResult, bool UpperTriangle, uint32_t QueryGroup>
2317 uint32_t const* query_planes,
2318 uint32_t query_id_offset,
2319 uint32_t query_count,
2320 uint32_t const* reference_planes,
2321 uint32_t reference_count,
2322 SearchResult* results,
2323 uint32_t* cell_counter,
2324 uint32_t const* reference_mask
2325) {
2326 static_assert(match_filter_supported<BucketCount>);
2327 constexpr uint32_t warp_width = 32;
2328 constexpr uint32_t group = QueryGroup;
2329 static_assert(group >= 1U && group <= warp_width);
2330 constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U);
2331 constexpr uint32_t lane_groups = static_cast<uint32_t>(BucketCount / warp_width / warp_width);
2332 constexpr uint32_t max_block_words = static_cast<uint32_t>(cuda::std::max<size_t>(
2333 1U, refine_block_row_bytes / (BucketCount * sizeof(uint16_t)) / warp_width
2334 ));
2335 extern __shared__ uint4 all_pairs_query_planes[];
2336 __shared__ uint32_t next_word;
2337 constexpr uint32_t part_width = warp_width / all_pairs_word_parts;
2338 __shared__ uint32_t claimed_cell;
2339
2340 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
2341 auto const words_per_query = candidate_bit_words(reference_count);
2342 auto const cells = make_refine_cells<UpperTriangle>(
2344 );
2345 auto const groups = cells.groups;
2346
2347 for (auto cell = next_cell(cell_counter, blockIdx.x - gridDim.x, claimed_cell);
2348 cell < cells.cell_count;
2349 cell = next_cell(cell_counter, cell, claimed_cell)) {
2350 auto const first_query = (cell % groups) * group;
2351 auto const group_size = cuda::std::min(group, query_count - first_query);
2352 auto const first_word = cells.first_word + (cell / groups) * cells.block_words;
2353 auto const last_word = cuda::std::min(first_word + cells.block_words, words_per_query);
2354 auto const* group_planes = reinterpret_cast<uint4 const*>(
2355 query_planes + static_cast<size_t>(first_query) * row_words
2356 );
2357
2358 __syncthreads();
2359 if (threadIdx.x == 0U) {
2361 }
2362 for (auto i = static_cast<uint32_t>(threadIdx.x); i < group_size * (row_words / 4U);
2363 i += all_pairs_block_size) {
2364 all_pairs_query_planes[i] = group_planes[i];
2365 }
2366 __syncthreads();
2367
2368 for (;;) {
2369 uint32_t word = 0U;
2370 if (lane == 0U) {
2371 word = atomicAdd(&next_word, 1U);
2372 }
2373 word = __shfl_sync(0xffffffffU, word, 0);
2374 auto const part = word % all_pairs_word_parts;
2376 if (word >= last_word) {
2377 break;
2378 }
2379 // Lane m holds query m's selected references in this part of the word.
2380 uint32_t bits = 0U;
2381 if (lane < group_size) {
2382 auto const first_reference = word * warp_width;
2383 auto const left = reference_count - first_reference;
2384 bits = left >= warp_width ? ~0U : (1U << left) - 1U;
2385 bits &= ((1U << part_width) - 1U) << (part * part_width);
2386 if constexpr (UpperTriangle) {
2387 auto const query_id = query_id_offset + first_query + lane;
2388 if (first_reference <= query_id) {
2389 auto const skipped = query_id - first_reference + 1U;
2390 bits &= skipped >= warp_width ? 0U : ~0U << skipped;
2391 }
2392 }
2393 if (reference_mask != nullptr) {
2395 }
2396 }
2397 auto remaining = __reduce_or_sync(0xffffffffU, bits);
2398 while (remaining != 0U) {
2399 auto const bit_a = static_cast<uint32_t>(__ffs(remaining) - 1);
2400 remaining &= remaining - 1U;
2401 auto const has_b = remaining != 0U;
2402 auto const bit_b = has_b ? static_cast<uint32_t>(__ffs(remaining) - 1) : bit_a;
2403 remaining &= remaining - 1U;
2404 auto const reference_a = word * warp_width + bit_a;
2405 auto const reference_b = word * warp_width + bit_b;
2406 auto const* planes_a = reinterpret_cast<uint4 const*>(
2407 reference_planes + static_cast<size_t>(reference_a) * row_words
2408 );
2409 auto const* planes_b = reinterpret_cast<uint4 const*>(
2410 reference_planes + static_cast<size_t>(reference_b) * row_words
2411 );
2412 uint32_t a[lane_groups][score_planes];
2413 uint32_t b[lane_groups][score_planes];
2414 uint32_t a_any[lane_groups];
2415 uint32_t b_any[lane_groups];
2416 uint32_t nonzero = ~0U;
2417 _Pragma("unroll")
2418 for (uint32_t j = 0; j < lane_groups; ++j) {
2419 load_plane_group(planes_a, j * warp_width + lane, a[j]);
2420 load_plane_group(planes_b, j * warp_width + lane, b[j]);
2421 a_any[j] = plane_any(a[j]);
2422 b_any[j] = plane_any(b[j]);
2423 nonzero &= a_any[j] & b_any[j];
2424 }
2425 // Selections are prefixes of the query group, and B's includes A's.
2426 auto const selected_a = __ballot_sync(0xffffffffU, (bits >> bit_a) & 1U);
2427 auto const selected_b = __ballot_sync(0xffffffffU, (bits >> bit_b) & 1U);
2428 auto const members = static_cast<uint32_t>(__popc(selected_b));
2429 // Lane m keeps member m's totals for both references.
2430 pairwise_counts mine_a{};
2431 pairwise_counts mine_b{};
2432 auto const compare = [&](auto full) {
2433 for (uint32_t m = 0U; m < members; ++m) {
2434 auto const* member =
2435 all_pairs_query_planes + static_cast<size_t>(m) * (row_words / 4U);
2436 plane_counts counts_a{};
2437 plane_counts counts_b{};
2438 _Pragma("unroll")
2439 for (uint32_t j = 0; j < lane_groups; ++j) {
2440 uint32_t q[score_planes];
2441 load_plane_group(member, j * warp_width + lane, q);
2442 compare_plane_group<false, decltype(full)::value>(
2443 q, a[j], a_any[j], 0U, 0U, 0U, counts_a
2444 );
2445 compare_plane_group<false, decltype(full)::value>(
2446 q, b[j], b_any[j], 0U, 0U, 0U, counts_b
2447 );
2448 }
2449 auto const total_a = counts_a.reduce<BucketCount, decltype(full)::value>();
2450 auto const total_b = counts_b.reduce<BucketCount, decltype(full)::value>();
2451 if (lane == m) {
2452 mine_a = total_a;
2453 mine_b = total_b;
2454 }
2455 }
2456 };
2457 if (__all_sync(0xffffffffU, nonzero == ~0U)) {
2458 compare(cuda::std::true_type{});
2459 } else {
2460 compare(cuda::std::false_type{});
2461 }
2462 // The slot implies the pair; only its counts are stored.
2463 if (((selected_a >> lane) & 1U) != 0U) {
2464 results[batch_result_slot<UpperTriangle>(
2465 first_query + lane, reference_a, query_id_offset, reference_count
2466 )] = SearchResult::pack(mine_a.lower, mine_a.equal, mine_a.higher);
2467 }
2468 if (has_b && ((selected_b >> lane) & 1U) != 0U) {
2469 results[batch_result_slot<UpperTriangle>(
2470 first_query + lane, reference_b, query_id_offset, reference_count
2471 )] = SearchResult::pack(mine_b.lower, mine_b.equal, mine_b.higher);
2472 }
2473 }
2474 }
2475 }
2476}
2477
2484template <size_t BucketCount, typename SearchResult, bool UpperTriangle, bool Swar>
2485__global__ __launch_bounds__(block_size) void row_major_all_pairs_kernel(
2486 uint16_t const* rows,
2487 uint32_t query_id_offset,
2488 uint32_t query_count,
2489 uint32_t reference_count,
2490 SearchResult* results,
2491 uint32_t const* reference_mask
2492) {
2493 constexpr uint32_t warp_width = 32;
2494 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
2495 auto const warps = uint64_t{gridDim.x} * (block_size / warp_width);
2496 auto const pairs = uint64_t{query_count} * reference_count;
2497 for (auto pair = (uint64_t{blockIdx.x} * block_size + threadIdx.x) / warp_width; pair < pairs;
2498 pair += warps) {
2499 auto const query = static_cast<uint32_t>(pair / reference_count);
2500 auto const reference_id = static_cast<uint32_t>(pair % reference_count);
2501 auto const query_id = query_id_offset + query;
2502 if (UpperTriangle && reference_id <= query_id) continue;
2503 if (reference_mask != nullptr &&
2504 ((reference_mask[reference_id / 32U] >> (reference_id % 32U)) & 1U) == 0U) {
2505 continue;
2506 }
2507 auto const* query_row = rows + size_t{query_id} * BucketCount;
2508 auto const* reference_row = rows + size_t{reference_id} * BucketCount;
2509 pairwise_counts counts{};
2510 if constexpr (Swar) {
2511 for (auto chunk = lane; chunk < BucketCount / 16U; chunk += warp_width) {
2512 uint32_t q[8];
2513 load_256_global_nc(reinterpret_cast<uint32_t const*>(query_row + chunk * 16U), q);
2514 classify_query_words(counts, q, reference_row + chunk * 16U);
2515 }
2516 } else {
2517 for (auto bucket = lane; bucket < BucketCount; bucket += warp_width) {
2518 classify(counts, query_row[bucket], reference_row[bucket]);
2519 }
2520 }
2521 auto const lower = __reduce_add_sync(0xffffffffU, counts.lower);
2522 auto const equal = __reduce_add_sync(0xffffffffU, counts.equal);
2523 auto const higher = __reduce_add_sync(0xffffffffU, counts.higher);
2524 if (lane == 0U) {
2525 results[batch_result_slot<UpperTriangle>(
2526 query, reference_id, query_id_offset, reference_count
2527 )] = SearchResult::pack(lower, equal, higher);
2528 }
2529 }
2530}
2531
2534 uint32_t empty{};
2535 float restored{};
2536
2538 friend __device__ cardinality_payload
2540 return {left.empty + right.empty, left.restored + right.restored};
2541 }
2542};
2543
2549template <size_t BucketCount, typename Layout = default_register_layout>
2550__global__ void
2551cardinality_kernel(uint32_t const* registers, uint64_t* empty_out, double* estimate_out) {
2552 using block_reduce = cub::BlockReduce<cardinality_payload, block_size>;
2553 __shared__ typename block_reduce::TempStorage storage;
2555 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2556 bucket += blockDim.x) {
2557 auto const stored = static_cast<uint16_t>(__ldcs(&registers[bucket]));
2558 if (stored == 0U) {
2559 ++local.empty;
2560 } else {
2561 local.restored += static_cast<float>(restore_midpoint<Layout>(stored));
2562 }
2563 }
2564 auto const total = block_reduce(storage).Sum(local);
2565 if (threadIdx.x == 0) {
2566 *empty_out = total.empty;
2567 *estimate_out = static_cast<double>(cardinality_f32(
2568 static_cast<float>(BucketCount), static_cast<float>(total.empty), total.restored
2569 ));
2570 }
2571}
2572
2574template <size_t BucketCount, typename Layout = default_register_layout>
2576 uint32_t const* const registers,
2577 hybrid_cardinality_estimates* const estimates
2578) {
2579 __shared__ uint32_t bins[nlz_bins];
2580 using block_histogram =
2581 cub::BlockHistogram<uint32_t, block_size, 1, nlz_bins, cub::BLOCK_HISTO_ATOMIC>;
2582 using block_reduce = cub::BlockReduce<float, block_size>;
2583 __shared__ typename block_reduce::TempStorage storage;
2584 block_histogram histogram;
2585 histogram.InitHistogram(bins);
2586 float local_restored = 0.0f;
2587 __syncthreads();
2588
2589 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2590 bucket += blockDim.x) {
2591 auto const stored = static_cast<uint16_t>(__ldcs(&registers[bucket]));
2592 uint32_t bin[1]{
2593 stored == 0U ? 0U : static_cast<uint32_t>(stored >> Layout::mantissa_bits) + 1U
2594 };
2595 histogram.Composite(bin, bins);
2596 if (stored != 0U) {
2597 local_restored += static_cast<float>(restore<Layout>(stored));
2598 }
2599 }
2600 __syncthreads();
2601 auto const restored = block_reduce(storage).Sum(local_restored);
2602 if (threadIdx.x == 0) {
2603 *estimates = hybrid_estimates_f32(bins, static_cast<float>(BucketCount), restored);
2604 }
2605}
2606
2608template <size_t BucketCount, hybrid_variant Variant, typename Layout = default_register_layout>
2609__global__ void
2610hybrid_cardinality_variant_kernel(uint32_t const* const registers, double* const estimate) {
2611 __shared__ uint32_t bins[nlz_bins];
2612 using block_histogram =
2613 cub::BlockHistogram<uint32_t, block_size, 1, nlz_bins, cub::BLOCK_HISTO_ATOMIC>;
2614 using block_reduce = cub::BlockReduce<float, block_size>;
2615 __shared__ typename block_reduce::TempStorage storage;
2616 block_histogram histogram;
2617 histogram.InitHistogram(bins);
2618 float local_restored = 0.0f;
2619 __syncthreads();
2620
2621 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2622 bucket += blockDim.x) {
2623 auto const stored = static_cast<uint16_t>(__ldcs(&registers[bucket]));
2624 uint32_t bin[1]{
2625 stored == 0U ? 0U : static_cast<uint32_t>(stored >> Layout::mantissa_bits) + 1U
2626 };
2627 histogram.Composite(bin, bins);
2628 if (stored != 0U) {
2629 local_restored += static_cast<float>(restore<Layout>(stored));
2630 }
2631 }
2632 __syncthreads();
2633 auto const restored = block_reduce(storage).Sum(local_restored);
2634 if (threadIdx.x == 0) {
2635 auto const estimates =
2636 hybrid_estimates_f32(bins, static_cast<float>(BucketCount), restored);
2637 if constexpr (Variant == hybrid_variant::bbtools) {
2638 *estimate = estimates.bbtools;
2639 } else {
2640 *estimate = estimates.paper;
2641 }
2642 }
2643}
2644
2648template <size_t BucketCount, typename Layout = default_register_layout>
2650 uint32_t const* const registers,
2651 uint32_t const row_count,
2652 uint64_t* const empty_out,
2653 double* const estimates_out
2654) {
2655 auto const row = static_cast<size_t>(blockIdx.x);
2656 if (row >= row_count) {
2657 return;
2658 }
2659 using block_reduce = cub::BlockReduce<cardinality_payload, block_size>;
2660 __shared__ typename block_reduce::TempStorage storage;
2661 auto const* const mine = registers + row * BucketCount;
2663 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2664 bucket += blockDim.x) {
2665 auto const stored = static_cast<uint16_t>(__ldcs(&mine[bucket]));
2666 if (stored == 0U) {
2667 ++local.empty;
2668 } else {
2669 local.restored += static_cast<float>(restore_midpoint<Layout>(stored));
2670 }
2671 }
2672 auto const total = block_reduce(storage).Sum(local);
2673 if (threadIdx.x == 0) {
2674 empty_out[row] = total.empty;
2675 estimates_out[row] = static_cast<double>(cardinality_f32(
2676 static_cast<float>(BucketCount), static_cast<float>(total.empty), total.restored
2677 ));
2678 }
2679}
2680
2682template <size_t BucketCount>
2683__global__ void batch_scores_kernel(
2684 uint32_t const* const registers,
2685 uint32_t const row_count,
2686 uint16_t* const scores_out
2687) {
2688 auto const row = static_cast<size_t>(blockIdx.x);
2689 if (row >= row_count) {
2690 return;
2691 }
2692 auto const* const mine = registers + row * BucketCount;
2693 auto* const scores = scores_out + row * BucketCount;
2694 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2695 bucket += blockDim.x) {
2696 scores[bucket] = static_cast<uint16_t>(__ldcs(&mine[bucket]));
2697 }
2698}
2699
2700} // namespace cuddl::detail
__global__ void cardinality_kernel(uint32_t const *registers, uint64_t *empty_out, double *estimate_out)
Computes the cardinality of a single constructed sketch.
Definition kernels.cuh:2551
constexpr uint32_t row_words
Definition kernels.cuh:1546
auto const long_ranges_used
Definition kernels.cuh:1152
constexpr uint32_t max_block_words
Definition kernels.cuh:1867
uint32_t warp_grid_blocks(size_t warps) noexcept
Blocks for a grid-stride launch covering warps warps of block_size threads.
Definition kernels.cuh:1524
__global__ size_t query_row_offset
Definition kernels.cuh:599
constexpr uint32_t index_match_cells_per_warp
Cells one warp owns per iteration of count_batch_index_matches_kernel: one per lane.
Definition kernels.cuh:392
__global__ uint16_t const * sorted_keys
Definition kernels.cuh:469
__global__ uint32_t reference_count
Definition kernels.cuh:437
auto const * query
Definition kernels.cuh:1030
__device__ __forceinline__ void write_score_planes(uint32_t score, uint32_t group, uint32_t lane, uint32_t *planes)
Writes the bit-planes of one 32-bucket group whose scores the warp holds, one per lane.
Definition kernels.cuh:1340
_Pragma("unroll") for(uint32_t bit=0U
auto const high
Definition kernels.cuh:940
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const uint32_t uint32_t bool uint32_t uint32_t const uint32_t const * bitmap_starts
Definition kernels.cuh:931
auto const shift
Definition kernels.cuh:442
__global__ uint32_t const uint32_t const * pass_bits
Definition kernels.cuh:1784
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const uint32_t uint32_t bool uint32_t * candidate_bits
Definition kernels.cuh:929
auto const skipped
Definition top_k.cuh:206
constexpr uint32_t match_filter_block_size
Threads per match_filter_kernel block.
Definition kernels.cuh:2090
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t word_end
Definition kernels.cuh:1789
__shared__ uint32_t packed_counts[]
constexpr bool match_filter_supported
Whether match_filter_kernel supports BucketCount: a lane caches at most two plane groups of each of i...
Definition kernels.cuh:2095
__host__ __device__ uint64_t batch_result_slot(uint32_t query_index, uint32_t reference_id, uint32_t query_id_offset, uint32_t reference_count) noexcept
Position of the pair (query_index of the tile, reference_id) in a tile's results: rows of reference_c...
Definition kernels.cuh:1751
__global__ void batch_scores_kernel(uint32_t const *const registers, uint32_t const row_count, uint16_t *const scores_out)
Copies the winning score of every register into compact row-major scores.
Definition kernels.cuh:2683
__global__ uint32_t uint32_t uint32_t * directory
Definition kernels.cuh:440
constexpr uint32_t posting_range_block_size
Threads of the single index_posting_ranges_kernel block.
Definition kernels.cuh:527
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const * ranges
Definition kernels.cuh:925
auto const cells
Definition kernels.cuh:1878
auto const lane
Definition kernels.cuh:966
__global__ uint32_t uint32_t * planes
Definition kernels.cuh:1358
__device__ __forceinline__ void load_plane_group(uint4 const *row, uint32_t group, uint32_t(&words)[score_planes])
Loads group group of one bit-plane row (score_plane_index layout).
Definition kernels.cuh:1380
static __global__ uint32_t const uint32_t uint32_t const uint32_t * match_counts
Definition kernels.cuh:576
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const uint32_t minimum_matches
Definition kernels.cuh:926
constexpr uint32_t cached_groups
Definition kernels.cuh:1866
__global__ uint32_t const * reference_planes
Definition kernels.cuh:1539
auto const pairs
Definition kernels.cuh:1592
constexpr uint32_t bitmap_refine_waves
Cells per refine_batch_bitmap_kernel block that a tile of bitmap-selected pairs is split into when it...
Definition kernels.cuh:1655
auto const indexed_groups
Definition kernels.cuh:1594
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const uint32_t uint32_t bool uint32_t uint32_t const uint32_t const uint32_t const * bitmap_counts
Definition kernels.cuh:933
auto const count
Definition kernels.cuh:1031
__device__ void refine_write_result(uint32_t index, uint32_t query_index, uint32_t reference_id, uint32_t query_id_offset, uint32_t const *match_counts, uint32_t pair_id, pairwise_counts local, uint32_t lane, SearchResult *results, uint32_t *result_match_counts)
Warp-reduces one refined candidate and writes its stable result slot.
Definition kernels.cuh:1290
__global__ uint32_t uint32_t indexed_bucket_count
Definition kernels.cuh:438
__device__ __forceinline__ void load_plane_chunk(uint4 const *row, uint32_t group, uint32_t chunk, uint32_t(&words)[4])
Loads plane chunk chunk (planes 4 chunk ... 4 chunk + 3) of group group of a bit-plane row (score_pla...
Definition kernels.cuh:2101
refine_candidates
Pairs refine_batch_bitmap_kernel compares.
Definition kernels.cuh:1737
@ reference_mask
Every pair of the layout whose reference is set in one reference-wide bitmap.
@ bitmap
Exactly the pairs set in a pass bitmap.
@ all
Every pair of the layout, or with pass bits requested, those meeting the threshold.
__global__ size_t uint32_t bool blacklist_view bool IncludeCardinality
Definition kernels.cuh:155
__global__ size_t uint32_t bool blacklist_view blacklist
Definition kernels.cuh:96
auto const thresholded
Definition kernels.cuh:1888
constexpr uint32_t all_pairs_block_size
Threads per refine_all_pairs_kernel block.
Definition kernels.cuh:2299
constexpr uint32_t all_pairs_word_parts
Parts a refine_all_pairs_kernel warp claims each 32-reference word in.
Definition kernels.cuh:2305
auto const query_index
Definition kernels.cuh:938
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t key_mask
Definition kernels.cuh:540
__host__ __device__ float cardinality_f32(float bucket_count, float empty_count, float sum_restored) noexcept
Single-precision cardinality for the throughput-bound cardinality reduction.
__global__ uint16_t const uint32_t uint32_t uint32_t key_count
Definition kernels.cuh:472
__device__ hybrid_cardinality_estimates hybrid_estimates_f32(uint32_t const *bins, float bucket_count, float restored_sum) noexcept
Single-precision hybrid_estimates for the latency-bound hybrid scan.
auto const warp
Definition kernels.cuh:967
__device__ void classify(pairwise_counts &target, uint32_t this_score, uint32_t other_score) noexcept
Classifies one score relative to this into target.
__global__ void hybrid_cardinality_variant_kernel(uint32_t const *const registers, double *const estimate)
Computes one HybridDDL variant estimate for estimator comparisons.
Definition kernels.cuh:2610
__device__ uint2 lane_posting_range(uint32_t const *offsets, uint16_t const *sorted_keys, uint32_t reference_count, uint32_t bucket, uint32_t key, uint32_t key_count)
One thread's posting range for a bucket/key cell of a dense or sparse index.
Definition kernels.cuh:395
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t uint32_t * range_end_scan
Definition kernels.cuh:543
constexpr uint32_t match_filter_planes
Low planes match_filter_kernel compares.
Definition kernels.cuh:2110
constexpr uint32_t block_size
Threads per CTA for the single-pair and cardinality reduction kernels.
Definition kernels.cuh:28
constexpr uint32_t bitmap_refine_group
Queries one refine_batch_bitmap_kernel block refines together.
Definition kernels.cuh:1670
__global__ uint32_t uint32_t uint64_t uint32_t * ids
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t word_begin
Definition kernels.cuh:1788
auto const groups
Definition kernels.cuh:1886
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t tile
Definition kernels.cuh:922
constexpr uint32_t shared_construction_block_size
Threads per CTA for the CTA-local construction kernel.
Definition kernels.cuh:78
constexpr uint32_t score_planes
Bits per score, and so bit-planes per 32-bucket group.
Definition kernels.cuh:1326
__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t * result_match_counts
Definition kernels.cuh:1852
__device__ __forceinline__ void compare_plane_group(uint32_t const (&q)[score_planes], uint32_t const (&r)[score_planes], uint32_t reference_any, uint32_t reference_low_any, uint16_t key_mask, uint32_t match_lanes, plane_counts &counts) noexcept
Adds one group's comparison of query planes q with reference planes r, whose nonzero buckets referenc...
Definition kernels.cuh:1457
__global__ void refine_all_pairs_kernel(uint32_t const *query_planes, uint32_t query_id_offset, uint32_t query_count, uint32_t const *reference_planes, uint32_t reference_count, SearchResult *results, uint32_t *cell_counter, uint32_t const *reference_mask)
Exactly compares the layout pairs selected by reference_mask, without match counts.
Definition kernels.cuh:2316
__global__ size_t input_size
Definition kernels.cuh:93
__host__ __device__ constexpr uint32_t sparse_directory_shift(uint32_t reference_count) noexcept
Low key bits a sparse key directory cell spans for reference_count references.
Definition kernels.cuh:420
constexpr uint32_t warps_per_block
Definition kernels.cuh:935
unsigned long long local
Definition kernels.cuh:478
__host__ __device__ double cardinality(double bucket_count, double empty_count, double sum_restored) noexcept
Public DDL cardinality estimate.
constexpr size_t bitmap_refine_dynamic_bytes
Shared bytes one refine_batch_bitmap_kernel block stages.
Definition kernels.cuh:1680
constexpr uint32_t index_tile_lane_postings
Longest posting range one count_batch_index_tile_kernel lane walks alone.
Definition kernels.cuh:682
auto const total
Definition kernels.cuh:443
__global__ size_t uint32_t * registers
Definition kernels.cuh:94
__global__ uint32_t const uint32_t const uint32_t const uint32_t first_query_id
Definition kernels.cuh:1786
__global__ uint32_t uint32_t first_word
Definition top_k.cuh:234
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult * packed
Definition kernels.cuh:1791
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t * range_begin
Definition kernels.cuh:541
__device__ __forceinline__ refine_cells make_refine_cells(uint32_t query_id_offset, uint32_t query_count, uint32_t group, uint32_t words_per_query, uint32_t max_block_words, uint32_t waves)
Splits a tile into refine_cells, about waves cells per block when the tile allows,...
Definition kernels.cuh:1717
__host__ __device__ constexpr uint64_t hash_kmer(uint64_t packed_kmer) noexcept
Hashes a canonical packed k-mer into the value space used by DDL registers.
Definition hash.cuh:21
bool uses_tiled_index_counts(uint32_t reference_count, uint32_t query_count, uint32_t indexed_bucket_count) noexcept
Whether a batch counts through count_batch_index_tile_kernel.
Definition kernels.cuh:691
constexpr uint32_t word_slices
Definition kernels.cuh:1871
__device__ __forceinline__ uint32_t plane_any(uint32_t const (&planes)[score_planes]) noexcept
Buckets of a plane group holding a nonzero score.
Definition kernels.cuh:1421
__global__ size_t uint32_t query_count
Definition kernels.cuh:600
__global__ uint32_t const uint32_t uint32_t const uint32_t const SearchResult * results
Definition kernels.cuh:1544
constexpr size_t bitmap_refine_shared_bytes
Shared bytes refine_batch_bitmap_kernel may stage query planes in; one block per SM fits on every sup...
Definition kernels.cuh:1659
constexpr uint32_t wide_chunk_buckets
Buckets one contiguous chunk covers, for a query/reference score pair.
Definition kernels.cuh:74
__global__ void hybrid_posting_ranges_kernel(uint16_t const *queries, uint32_t const *offsets, uint32_t const *bitmap_cells, uint32_t bitmap_count, uint32_t words, uint32_t indexed_buckets, uint16_t key_mask, uint2 *ranges, uint32_t *bitmap_starts, uint32_t *bitmap_counts)
Splits each query's posting ranges into short lists and bitmap offsets.
Definition kernels.cuh:775
constexpr bool staged
Definition kernels.cuh:1860
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const uint32_t uint32_t query_id_offset
Definition kernels.cuh:927
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t packed_base
Definition kernels.cuh:1790
constexpr size_t maximum_batch_rows
Stored sketches one batch launch may index, bounded by the CUDA grid dimension.
Definition kernels.cuh:31
auto const words
Definition top_k.cuh:204
__device__ __forceinline__ uint32_t plane_low_any(uint32_t const (&planes)[score_planes]) noexcept
Buckets of a plane group whose score is nonzero below the top plane.
Definition kernels.cuh:1432
__host__ __device__ constexpr uint32_t sparse_directory_entries(uint32_t reference_count) noexcept
Bounds per bucket in a sparse key directory.
Definition kernels.cuh:429
__shared__ uint32_t long_count
Definition kernels.cuh:965
constexpr uint32_t groups_per_row
Definition kernels.cuh:1360
__global__ __launch_bounds__(shared_construction_block_size) void add_shared_kernel(uint64_t const *input
Constructs a sketch through a CTA-local shared-memory register array.
__shared__ uint32_t claimed_cell
Definition kernels.cuh:1874
__global__ uint16_t const uint32_t uint32_t uint32_t unsigned long long * work
Definition kernels.cuh:474
__global__ size_t uint32_t bool vector_input
Definition kernels.cuh:95
__device__ uint32_t sum_posting_bitmaps(uint32_t const *bitmaps, uint32_t const *starts, uint32_t count, uint32_t word, uint32_t minimum_matches, uint32_t passed, uint32_t(&counters)[CounterBits])
Sums one query's posting bitmaps over one word of references in bit-sliced counters.
Definition kernels.cuh:844
__global__ uint32_t row_count
Definition kernels.cuh:1358
__device__ __forceinline__ void load_256_global_nc(const T *ptr, T *out)
Loads 256 bits from global memory using the non-coherent cache path.
Definition helpers.cuh:47
constexpr uint32_t group
Definition kernels.cuh:1858
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult uint32_t * packed_match_counts
Definition kernels.cuh:1793
auto const words_per_query
Definition kernels.cuh:1795
__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t uint32_t uint32_t uint32_t * cell_counter
Definition kernels.cuh:1856
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const uint32_t uint32_t bool all_to_all
Definition kernels.cuh:928
auto const items
Definition kernels.cuh:1549
__device__ __forceinline__ uint32_t next_cell(uint32_t *cell_counter, uint32_t cell, uint32_t &claimed)
The cell a refinement-style block processes after cell: claimed from cell_counter,...
Definition kernels.cuh:1690
__shared__ uint32_t next_word
Definition kernels.cuh:1873
__shared__ block_scan::TempStorage storage
Definition kernels.cuh:545
__device__ uint2 directory_posting_range(uint16_t const *sorted_keys, uint32_t const *directory, uint32_t reference_count, uint32_t bucket, uint32_t sparse_key)
One sparse bucket/key posting range, narrowed through the bucket's key directory.
Definition kernels.cuh:505
__global__ void match_filter_kernel(uint32_t const *query_planes, uint32_t query_id_offset, uint32_t query_count, uint32_t const *reference_planes, uint32_t reference_count, uint32_t indexed_bucket_count, uint32_t minimum_matches, uint32_t *candidate_bits, uint32_t *cell_counter)
Records in candidate_bits (candidate-bitmap layout) the pairs of the layout whose scores agree in the...
Definition kernels.cuh:2125
__device__ __forceinline__ void classify_query_words(pairwise_counts &target, uint32_t const (&q)[8], ReferenceScore const *reference) noexcept
Classifies 16 buckets of an already loaded 16-bit query chunk against one load_256_alignment aligned ...
Definition kernels.cuh:1241
constexpr uint32_t index_tile_long_capacity
Long posting ranges one count_batch_index_tile_kernel block walks together.
Definition kernels.cuh:685
auto const stride
Definition kernels.cuh:578
constexpr uint32_t bitmap_refine_block_size
Threads per refine_batch_bitmap_kernel block.
Definition kernels.cuh:1649
__shared__ uint4 query_plane_storage[]
static __global__ uint32_t const * offsets
Definition kernels.cuh:536
for(auto i=static_cast< size_t >(blockIdx.x) *block_size+threadIdx.x;i< total;i+=static_cast< size_t >(gridDim.x) *block_size)
Definition kernels.cuh:444
__host__ __device__ constexpr uint32_t score_plane_index(uint32_t group, uint32_t plane) noexcept
Position of plane plane of 32-bucket group group within a row's bit-planes.
Definition kernels.cuh:1334
__global__ uint32_t uint32_t uint32_t SearchResult uint32_t const * reference_mask
Definition kernels.cuh:2492
__global__ __maxnreg__(48) void count_batch_index_tile_kernel(QueryRow const *queries
Counts index matches for one query against one reference tile in shared memory.
constexpr size_t refine_block_row_bytes
Reference-row bytes one refine_batch_index_candidates_kernel block spans.
Definition kernels.cuh:1323
auto const warps
Definition kernels.cuh:2495
constexpr int32_t nlz_bins
NLZ histogram bins for the hybrid estimator, including the empty tier.
__global__ void hybrid_cardinality_kernel(uint32_t const *const registers, hybrid_cardinality_estimates *const estimates)
Computes BBTools and paper-style HybridDDL estimates in one register scan.
Definition kernels.cuh:2575
auto *const bits
Definition kernels.cuh:1184
auto const tiles
Definition kernels.cuh:937
uint32_t carry
Definition kernels.cuh:547
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const uint32_t uint32_t bool uint32_t uint32_t const * bitmaps
Definition kernels.cuh:930
static __global__ uint32_t const uint32_t uint32_t const * postings
Definition kernels.cuh:574
auto const width
Definition kernels.cuh:963
__global__ uint32_t const uint32_t uint32_t const uint32_t const * candidate_count
Definition kernels.cuh:1542
auto const word
Definition top_k.cuh:241
__shared__ uint2 long_ranges[index_tile_long_capacity]
Definition kernels.cuh:964
constexpr uint32_t lane_groups
Definition kernels.cuh:1864
__global__ uint32_t const uint32_t const uint32_t const * word_offsets
Definition kernels.cuh:1785
__host__ __device__ uint16_t score(uint64_t hash) noexcept
Computes the 16-bit DDL rarity score for hash.
Definition register.cuh:43
__global__ uint32_t const uint32_t uint32_t const * candidate_ids
Definition kernels.cuh:1541
__global__ void batch_cardinality_kernel(uint32_t const *const registers, uint32_t const row_count, uint64_t *const empty_out, double *const estimates_out)
Computes the cardinality of every stored sketch in one launch.
Definition kernels.cuh:2649
__global__ size_t uint16_t * rows
Definition kernels.cuh:1499
constexpr bool bitmap_refine_staged
Whether a query group's planes fit bitmap_refine_shared_bytes.
Definition kernels.cuh:1663
uint32_t index_tile_references(uint32_t reference_count, uint32_t query_count, uint32_t multiprocessors, uint32_t max_shared_bytes) noexcept
References per tile, bounded by the device's shared memory and small enough that the batch launches a...
Definition kernels.cuh:702
auto const row
Definition top_k.cuh:202
auto const query_id
Definition top_k.cuh:201
@ counts
One 24-byte batch_search_result per pair: query ID, reference ID and the lower, equal,...
Exact membership within the candidate's destination bucket.
Definition blacklist.cuh:33
__device__ bool contains(uint64_t key, uint64_t hash, size_t bucket) const noexcept
hash must be hash_kmer(key); construction already holds it.
Definition blacklist.cuh:50
Per-thread accumulator feeding the CUB block reduction for cardinality.
Definition kernels.cuh:2533
friend __device__ cardinality_payload operator+(cardinality_payload left, cardinality_payload right) noexcept
Sums two accumulators for the block reduction.
Definition kernels.cuh:2539
Bit-sliced pairwise counts of one 32-bucket group, accumulated per lane.
Definition kernels.cuh:1395
uint32_t occupied
Buckets whose scores differ or whose reference score is nonzero: all but both-empty.
Definition kernels.cuh:1400
uint32_t differ
Buckets whose scores differ.
Definition kernels.cuh:1398
__device__ pairwise_counts reduce() const noexcept
Warp-reduces the counts into an exact pairwise summary.
Definition kernels.cuh:1405
Selects posting lists whose bitmap uses no more storage than their reference IDs.
Definition kernels.cuh:717
__device__ bool operator()(uint32_t cell) const
Definition kernels.cuh:722
How a refinement-style block splits a tile: cell c holds query group c % groups and the reference wor...
Definition kernels.cuh:1704
Per-thread accumulator feeding a CUB block reduction for fused summaries.
Definition kernels.cuh:34
friend __device__ summary_payload operator+(summary_payload left, summary_payload const &right) noexcept
Sums two accumulators for CUB block reductions.
Definition kernels.cuh:49
__device__ summary_payload & operator+=(summary_payload const &other) noexcept
Accumulates other into this instance for block reductions.
Definition kernels.cuh:40
Popcount of one pass-bit word.
Definition kernels.cuh:1768
__host__ __device__ uint32_t operator()(uint32_t word) const noexcept
Definition kernels.cuh:1769
Experimental DDL cardinality estimates computed from one register scan.
Raw per-bucket classification counts for one sketch pair comparison.
uint32_t both_empty
Both registers are empty in that bucket.
uint32_t equal
this register equals the other's in that bucket.
uint32_t lower
this register is strictly lower than the other's in that bucket.
uint32_t higher
this register is strictly higher than the other's in that bucket.
Pairwise counts with an optional compile-time cardinality field.