3#include <cub/device/device_radix_sort.cuh>
4#include <cub/device/device_scan.cuh>
5#include <cub/device/device_select.cuh>
7#include <cuda/iterator>
8#include <cuda/std/cstdint>
9#include <cuda/std/functional>
35template <
size_t BucketCount>
38template <
size_t BucketCount>
40 BucketCount * delta_query_group<BucketCount> *
sizeof(uint16_t) +
72 static_cast<uint32_t
>(
children.size()),
79template <
size_t BucketCount>
87 constexpr uint32_t spacing =
89 for (
auto i = blockIdx.x * blockDim.x + threadIdx.x; i <
count; i += gridDim.x * blockDim.x) {
95 key = (key ^
rows[
static_cast<size_t>(
row) * BucketCount + bucket]) *
96 0x9e3779b97f4a7c15ULL;
107)
void delta_run_heads_kernel(uint64_t
const*
keys, uint32_t
count, uint32_t* starts) {
108 for (
auto p = blockIdx.x * blockDim.x + threadIdx.x; p <
count; p += gridDim.x * blockDim.x) {
109 starts[p] = p == 0U ||
keys[p] !=
keys[p - 1U] ? p : 0U;
116 uint32_t
const* starts,
121 for (
auto p = blockIdx.x * blockDim.x + threadIdx.x; p <
count; p += gridDim.x * blockDim.x) {
126 auto const q = starts[p] + j;
127 out[j] = q < p ?
ids[q] : ~0U;
133template <
size_t BucketCount,
bool Closest>
135 uint16_t
const*
rows,
137 uint32_t
const* candidates,
138 uint8_t
const* is_base,
142 constexpr uint32_t vectors =
static_cast<uint32_t
>(BucketCount / 8U);
143 auto const lane = threadIdx.x % 32U;
144 for (
auto row = (blockIdx.x * blockDim.x + threadIdx.x) / 32U;
row <
count;
145 row += gridDim.x * blockDim.x / 32U) {
146 if constexpr (Closest) {
147 if (is_base[
row] != 0U || changes[
row] == 0U) {
151 auto target = Closest ? targets[
row] :
row;
152 uint32_t changed = Closest ? changes[
row] : 0U;
154 if constexpr (Closest) {
155 if (candidate != ~0U && (candidate == target || is_base[candidate] == 0U)) {
160 reinterpret_cast<uint4 const*
>(
rows +
static_cast<size_t>(
row) * BucketCount);
163 __reduce_max_sync(0xffffffffU, candidate == ~0U ? 0U : candidate + 1U);
168 reinterpret_cast<uint4 const*
>(
rows +
static_cast<size_t>(best - 1U) * BucketCount);
169 uint32_t differ = 0U;
170 for (
auto v =
lane; v < vectors; v += 32U) {
171 auto const a = own[v];
172 auto const b = other[v];
173 uint32_t
const words[4] = {a.x ^ b.x, a.y ^ b.y, a.z ^ b.z, a.w ^ b.w};
176 differ += ((x & 0xffffU) != 0U) + ((x >> 16U) != 0U);
179 differ = __reduce_add_sync(0xffffffffU, differ);
183 if (!Closest || differ == 0U) {
187 if (candidate == best - 1U) {
192 targets[
row] = target;
193 changes[
row] = changed;
200)
void delta_mark_bases_kernel(uint32_t
const* targets, uint32_t
count, uint8_t* is_base) {
201 for (
auto row = blockIdx.x * blockDim.x + threadIdx.x;
row <
count;
202 row += gridDim.x * blockDim.x) {
203 auto const target = targets[
row];
204 is_base[target] = 1U;
211 uint8_t
const* is_base,
217 auto const words = candidate_bit_words(
count);
218 for (
auto row = blockIdx.x * blockDim.x + threadIdx.x;
row <
words * 32U;
219 row += gridDim.x * blockDim.x) {
220 auto const base =
row <
count && is_base[
row] != 0U;
222 is_child[
row] = !base;
227 auto const bits = __ballot_sync(0xffffffffU, base);
228 if (
row % 32U == 0U) {
236template <
size_t BucketCount>
238 uint16_t
const*
rows,
239 uint32_t
const* child_ids,
240 uint32_t
const* child_count,
241 uint32_t
const* targets,
246 constexpr uint32_t
groups =
static_cast<uint32_t
>(BucketCount / 32U);
247 constexpr uint32_t row_bytes = delta_query_group<BucketCount> *
sizeof(uint16_t);
248 auto const lane = threadIdx.x % 32U;
249 auto const below = (1U <<
lane) - 1U;
250 for (
auto index = (blockIdx.x * blockDim.x + threadIdx.x) / 32U; index < *child_count;
251 index += gridDim.x * blockDim.x / 32U) {
252 auto const child = child_ids[index];
253 auto const base = targets[child];
254 auto const* after_row =
rows +
static_cast<size_t>(child) * BucketCount;
255 auto const* before_row =
rows +
static_cast<size_t>(base) * BucketCount;
257 uint32_t increases = 0U;
258 for (uint32_t g = 0; g <
groups; ++g) {
259 uint32_t
const before = before_row[g * 32U +
lane];
260 uint32_t
const after = after_row[g * 32U +
lane];
261 auto const changed = before != after;
262 auto const zero = changed && (before == 0U || after == 0U);
263 zeros +=
static_cast<uint32_t
>(__popc(__ballot_sync(0xffffffffU, zero)));
264 increases +=
static_cast<uint32_t
>(
265 __popc(__ballot_sync(0xffffffffU, changed && !zero && after > before))
268 auto const begin =
offsets[child];
270 uint32_t next[3] = {begin, begin + zeros, begin + zeros + increases};
271 for (uint32_t g = 0; g <
groups; ++g) {
272 auto const bucket = g * 32U +
lane;
273 uint32_t
const before = before_row[bucket];
274 uint32_t
const after = after_row[bucket];
275 auto const changed = before != after;
276 auto const low = min(before, after);
277 auto const kind =
low == 0U ? 0U : after > before ? 1U : 2U;
279 for (uint32_t k = 0; k < 3U; ++k) {
280 auto const members = __ballot_sync(0xffffffffU, changed && kind == k);
281 if (changed && kind == k) {
282 auto const width = max(before, after) -
low;
283 records[next[k] +
static_cast<uint32_t
>(__popc(members & below))] = {
285 (k == 0U ?
static_cast<uint32_t
>(after < before) :
low) |
width << 16U,
288 next[k] +=
static_cast<uint32_t
>(__popc(members));
292 children[index] = {child, base, begin,
count | zeros << 10U | increases << 20U};
299template <
size_t BucketCount>
300[[nodiscard]] Result<std::optional<reference_deltas>>
301build_reference_deltas(uint16_t
const*
rows, uint32_t
count, cuda::stream_ref stream) {
303 return std::optional<reference_deltas>{};
305 auto const device = stream.device();
309 cuda::make_device_buffer<uint32_t>(
315 cuda::make_device_buffer<uint64_t>(stream, device,
count, cuda::no_init)
318 cuda::make_device_buffer<uint64_t>(stream, device,
count, cuda::no_init)
321 cuda::make_device_buffer<uint32_t>(stream, device,
count, cuda::no_init)
324 cuda::make_device_buffer<uint32_t>(stream, device,
count, cuda::no_init)
327 cuda::make_device_buffer<uint32_t>(stream, device,
count, cuda::no_init)
330 delta_signature_kernel<BucketCount><<<grid,
block_size, 0, stream.get()>>>(
335 cub::DeviceRadixSort::SortPairs(
346 delta_run_heads_kernel<<<grid,
block_size, 0, stream.get()>>>(
351 cub::DeviceScan::InclusiveScan(
352 starts.data(), cuda::maximum<uint32_t>{},
static_cast<int64_t
>(
count), stream
355 delta_candidates_kernel<<<grid,
block_size, 0, stream.get()>>>(
356 sorted_ids.data(), starts.data(),
count,
signature, candidates.data()
366 delta_targets_kernel<BucketCount, false><<<warp_grid,
block_size, 0, stream.get()>>>(
370 auto is_base =
CUDDL_CUDA_TRY(cuda::make_device_buffer<uint8_t>(stream, device,
count, 0U));
374 cuda::make_device_buffer<uint32_t>(
375 stream, device, candidate_bit_words(
count), cuda::no_init
378 delta_mark_bases_kernel<<<grid,
block_size, 0, stream.get()>>>(
379 targets.data(),
count, is_base.data()
382 delta_targets_kernel<BucketCount, true><<<warp_grid,
block_size, 0, stream.get()>>>(
383 rows,
count, candidates.data(), is_base.data(), targets.data(),
offsets.data()
386 delta_classify_kernel<<<
390 stream.get()>>>(is_base.data(),
count,
offsets.data(), is_child.data(), base_bits.data());
393 cub::DeviceScan::ExclusiveSum(
offsets.data(),
static_cast<int64_t
>(
count + 1U), stream)
396 auto*
const child_ids = candidates.data();
397 auto*
const child_count = candidates.data() +
count;
399 cub::DeviceSelect::Flagged(
400 cuda::make_counting_iterator(uint32_t{0}),
404 static_cast<int64_t
>(
count),
408 uint32_t totals[2] = {};
410 &totals[0], child_count,
sizeof(uint32_t), cudaMemcpyDeviceToHost, stream.get()
413 &totals[1],
offsets.data() +
count,
sizeof(uint32_t), cudaMemcpyDeviceToHost, stream.get()
415 if (
auto const synced =
cuda_try([&] { stream.sync(); }); !synced) {
416 return Err(synced.error());
418 if (totals[0] == 0U) {
419 return std::optional<reference_deltas>{};
422 CUDDL_CUDA_TRY(cuda::make_device_buffer<uint4>(stream, device, totals[0], cuda::no_init));
424 cuda::make_device_buffer<uint2>(
425 stream, device, cuda::std::max(totals[1], 1U), cuda::no_init
428 auto deltas = reference_deltas{std::move(base_bits), std::move(children), std::move(records)};
429 delta_records_kernel<BucketCount>
436 deltas.children.data(),
437 deltas.records.data()
440 return std::optional<reference_deltas>{std::move(deltas)};
449template <
size_t BucketCount,
typename SearchResult,
bool UpperTriangle>
451 uint32_t const* query_planes,
455 reference_delta_view deltas,
458 constexpr uint32_t
group = delta_query_group<BucketCount>;
459 constexpr uint32_t slices = 32U /
group;
461 constexpr uint32_t bucket_groups =
static_cast<uint32_t
>(BucketCount / 32U);
462 extern __shared__ uint16_t
staged[];
463 auto const lane = threadIdx.x % 32U;
464 auto const warp = threadIdx.x / 32U;
469 auto const tiles_wanted = (2U * gridDim.x + query_groups) / cuda::std::max(1U, query_groups);
470 auto const child_tile = cuda::std::clamp(
471 (deltas.child_count + tiles_wanted - 1U) / tiles_wanted,
472 uint32_t{delta_min_child_tile},
473 uint32_t{delta_child_tile}
475 auto const child_tiles = (deltas.child_count + child_tile - 1U) / child_tile;
476 auto const*
const row =
reinterpret_cast<char const*
>(
staged) + member *
sizeof(uint16_t);
479 auto const load_records = [&](uint4 child, uint2(&values)[record_registers]) {
481 for (uint32_t k = 0; k < record_registers; ++k) {
482 auto const index =
lane + k * 32U;
483 values[k] = index < (child.w & 1023U) ? deltas.records[child.z + index] : uint2{};
486 for (
auto cell = blockIdx.x; cell < query_groups * child_tiles; cell += gridDim.x) {
488 auto const first_member = (cell / child_tiles) *
group;
490 auto const begin_child = (cell % child_tiles) * child_tile;
491 auto const end_child = cuda::std::min(deltas.child_count, begin_child + child_tile);
492 if constexpr (UpperTriangle) {
494 if (deltas.children[end_child - 1U].x <=
query_id_offset + first_member) {
500 auto const m = item %
group;
501 auto const bucket_group = item /
group;
506 reinterpret_cast<uint4 const*
>(
507 query_planes +
static_cast<size_t>(first_member + m) * (BucketCount / 2U)
520 auto const query = first_member + member;
521 auto child_index = begin_child +
warp;
522 if (child_index >= end_child) {
525 auto info = deltas.children[child_index];
526 uint2 pending[record_registers];
527 load_records(info, pending);
532 auto const count = info.w & 1023U;
535 for (uint32_t k = 0; k < record_registers; ++k) {
536 if (
lane + k * 32U <
count) buffer[
lane + k * 32U] = pending[k];
540 if (next_index < end_child) load_records(next_info, pending);
544 auto const child = info.x;
545 auto included = member < members;
546 if constexpr (UpperTriangle) {
549 if (__any_sync(0xffffffffU, included)) {
551 if (included && slice == 0U) {
552 base =
results[batch_result_slot<UpperTriangle>(
556 auto const zeros = (info.w >> 10U) & 1023U;
557 auto const increases = info.w >> 20U;
563 for (
auto i = slice; i < zeros; i += slices) {
564 auto const record = buffer[i];
565 uint32_t
const value = *
reinterpret_cast<uint16_t const*
>(
row + record.x);
566 auto const width = record.y >> 16U;
567 int const sign = (record.y & 1U) != 0U ? -1 : 1;
568 lower += value <
width ? sign : 0;
569 higher -= value - 1U <
width ? sign : 0;
570 empty_equal += value == 0U ? sign : 0;
573 auto const accumulate = [&](uint32_t begin, uint32_t end,
int& inside,
int& above) {
575 for (auto i = begin + slice; i < end; i += slices) {
576 auto const record = buffer[i];
577 uint32_t
const value = *
reinterpret_cast<uint16_t const*
>(
row + record.x);
578 auto const offset = value - (record.y & 0xffffU);
579 auto const width = record.y >> 16U;
580 inside += offset <
width;
581 above += offset - 1U <
width;
584 int increase_inside = 0;
585 int increase_above = 0;
586 int decrease_inside = 0;
587 int decrease_above = 0;
588 accumulate(zeros, zeros + increases, increase_inside, increase_above);
589 accumulate(zeros + increases,
count, decrease_inside, decrease_above);
590 lower += increase_inside - decrease_inside;
591 higher += decrease_above - increase_above;
593 for (uint32_t offset =
group; offset < 32U; offset *= 2U) {
594 lower += __shfl_xor_sync(0xffffffffU, lower, offset);
595 higher += __shfl_xor_sync(0xffffffffU, higher, offset);
596 empty_equal += __shfl_xor_sync(0xffffffffU, empty_equal, offset);
598 if (included && slice == 0U) {
599 auto const counts = base.unpack(
static_cast<uint32_t
>(BucketCount));
600 results[batch_result_slot<UpperTriangle>(
604 static_cast<uint32_t
>(
static_cast<int>(
counts.lower) + lower),
605 static_cast<uint32_t
>(
606 static_cast<int>(
counts.equal) - lower - higher + empty_equal
608 static_cast<uint32_t
>(
static_cast<int>(
counts.higher) + higher)
613 next_info = following_info;
#define CUDDL_CUDA_TRY(...)
Propagates a CUDA error wrapped in cuddl::Result<void>.
constexpr uint32_t delta_min_child_tile
constexpr uint32_t delta_signatures
Exact bucket signatures that propose base candidates, and buckets per signature.
uint32_t warp_grid_blocks(size_t warps) noexcept
Blocks for a grid-stride launch covering warps warps of block_size threads.
__global__ uint16_t const * sorted_keys
__global__ uint32_t reference_count
constexpr size_t delta_correction_shared_bytes
__global__ uint32_t uint32_t uint64_t * keys
_Pragma("unroll") for(uint32_t bit=0U
__global__ uint32_t uint32_t signature
constexpr uint32_t delta_change_limit
Most buckets in which a child reference may differ from its base reference.
__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).
constexpr uint32_t delta_child_tile
constexpr uint32_t delta_correction_warps
Warps per apply_reference_deltas_kernel block, and the most and fewest children one block cell takes.
constexpr uint32_t delta_query_group
Queries one correction cell stages bucket-major: 64 KiB of scores for every bucket count.
constexpr uint32_t block_size
Threads per CTA for the single-pair and cardinality reduction kernels.
__global__ uint32_t uint32_t uint64_t uint32_t * ids
constexpr uint32_t score_planes
Bits per score, and so bit-planes per 32-bucket group.
constexpr uint32_t delta_signature_candidates
Highest-ID rows taken from each matching signature.
__global__ size_t uint32_t query_count
__global__ uint32_t const uint32_t uint32_t const uint32_t const SearchResult * results
__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
constexpr uint32_t delta_signature_buckets
__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.
static __global__ uint32_t const * offsets
for(auto i=static_cast< size_t >(blockIdx.x) *block_size+threadIdx.x;i< total;i+=static_cast< size_t >(gridDim.x) *block_size)
constexpr uint32_t delta_candidates
__host__ __device__ uint16_t score(uint64_t hash) noexcept
Computes the 16-bit DDL rarity score for hash.
__global__ size_t uint16_t * rows
@ counts
One 24-byte batch_search_result per pair: query ID, reference ID and the lower, equal,...
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Result< void > cuda_try(cudaError_t error, std::source_location location=std::source_location::current())
Checks a CUDA runtime call and returns an error on failure.
Reference rows stored as a base reference plus the buckets that differ from it.
uint2 const * records
Per changed bucket: x is the bucket's byte offset in the bucket-major staged scores; y is low | width...
uint32_t const * base_bits
One bit per reference in the candidate-bitmap layout, set for bases.
uint4 const * children
Per child in ascending ID order: ID, base ID, first record, and count | zero_count << 10 | increase_c...
reference_delta_view view() const noexcept
cuda::device_buffer< uint4 > children
cuda::device_buffer< uint32_t > base_bits
cuda::device_buffer< uint2 > records