3#include <cub/device/device_segmented_sort.cuh>
5#include <cuda/iterator>
7#include <cuda/std/cstdint>
28[[nodiscard]] __host__ __device__
constexpr uint64_t
31 return (uint64_t{identity} << 32U) |
static_cast<uint32_t
>(~
id);
47 __host__ __device__ int64_t
operator()(int64_t i)
const noexcept {
57 return size_t{k} * 2U;
67 auto*
const values =
heap + k;
69 for (
auto child = 1U; child < k; child = 2U * at + 1U) {
70 auto child_key =
heap[child];
71 if (child + 1U < k &&
heap[child + 1U] < child_key) child_key =
heap[++child];
72 if (child_key >= key)
break;
74 values[at] = values[child];
83template <
bool Thresholded,
bool UpperTriangle>
84__device__
inline uint32_t
87 if constexpr (Thresholded) {
90 auto const left =
tile.reference_count -
word * 32U;
91 bits = left >= 32U ? ~0U : (1U << left) - 1U;
93 if constexpr (UpperTriangle) {
104template <
bool UpperTriangle>
107 auto const slot = batch_result_slot<UpperTriangle>(
115 uint32_t position = 0U;
116 asm(
"fns.b32 %0, %1, 0, %2;" :
"=r"(position) :
"r"(mask),
"r"(
static_cast<int>(n + 1U)));
129template <
typename Bits,
typename Entry>
130__device__
inline void
132 auto const lane =
static_cast<uint32_t
>(threadIdx.x) % 32U;
133 for (uint32_t base = 0U; base <
words; base += 32U) {
137 auto const count =
static_cast<uint32_t
>(cuda::std::popcount(
bits));
140 for (uint32_t offset = 1U; offset < 32U; offset *= 2U) {
141 auto const below = __shfl_up_sync(~0U, prefix, offset);
142 if (
lane >= offset) prefix += below;
144 auto const total = __shfl_sync(~0U, prefix, 31);
145 for (uint32_t first = 0U; first <
total; first += 32U) {
146 auto const candidate = first +
lane;
148 uint32_t source = 0U;
150 for (uint32_t step = 16U; step != 0U; step /= 2U) {
151 if (__shfl_sync(~0U, prefix, source + step - 1U) <= candidate) source += step;
153 auto const source_bits = __shfl_sync(~0U,
bits, source);
154 auto const before = __shfl_sync(~0U, prefix -
count, source);
155 uint64_t key = 0U, value = 0U;
156 if (candidate <
total) {
158 (base + source) * 32U +
top_k_nth_bit(source_bits, candidate - before),
163 for (
auto pending = __ballot_sync(~0U, key >
heap[0]); pending != 0U;
164 pending &= pending - 1U) {
165 if (
lane ==
static_cast<uint32_t
>(__ffs(pending) - 1) && key >
heap[0]) {
176template <
bool ToShared>
179 for (
auto i =
static_cast<uint32_t
>(threadIdx.x) % 32U; i < k; i += 32U) {
180 if constexpr (ToShared) {
192template <
bool Thresholded,
bool UpperTriangle>
196 extern __shared__ uint64_t heaps[];
197 auto const warp =
static_cast<uint32_t
>(threadIdx.x) / 32U;
198 auto const query =
static_cast<uint32_t
>(blockIdx.x) * (blockDim.x / 32U) +
warp;
212 return top_k_word_bits<Thresholded, UpperTriangle>(tile, query, skipped + word, words);
214 [&](uint32_t candidate, uint64_t& key, uint64_t& value) {
215 auto const reference = skipped * 32U + candidate;
216 auto const stored = top_k_stored<UpperTriangle>(tile, query, reference);
217 key = top_k_key(stored.unpack(tile.bucket_count), reference);
231template <
bool Thresholded>
235 auto const lane =
static_cast<uint32_t
>(threadIdx.x) % 32U;
236 auto const block =
static_cast<uint64_t
>(blockIdx.x) * (blockDim.x / 32U) + threadIdx.x / 32U;
237 auto const words =
tile.words_per_query();
249 for (uint32_t bit = 0U; bit < 32U; ++bit) {
250 auto const passing = __ballot_sync(~0U, ((
bits >> bit) & 1U) != 0U);
253 auto const genome =
word * 32U +
lane;
254 if (genome <
tile.reference_count) {
265 batch_result_tile
tile,
268 uint32_t first_genome
270 extern __shared__ uint64_t heaps[];
271 auto const warp =
static_cast<uint32_t
>(threadIdx.x) / 32U;
273 first_genome +
static_cast<uint32_t
>(blockIdx.x) * (blockDim.x / 32U) +
warp;
274 if (genome >=
tile.reference_count)
return;
278 auto const rows = cuda::std::min(
tile.query_count, genome -
tile.first_query_id);
284 return __ldg(columns + static_cast<size_t>(row_word) * tile.reference_count + genome);
286 [&](uint32_t
query, uint64_t& key, uint64_t& value) {
288 top_k_stored<true>(tile, query, genome).unpack(tile.bucket_count).mirrored();
289 key = top_k_key(counts, tile.first_query_id + query);
290 value = packed_pairwise_counts::pack(counts.lower, counts.equal, counts.higher).bits;
329 uint32_t genome_count,
330 uint32_t reference_count,
331 uint32_t bucket_count,
333 cuda::stream_ref stream,
334 uint32_t first_genome_id = 0U
338 int shared_limit = 0;
340 &shared_limit, cudaDevAttrMaxSharedMemoryPerBlockOptin, stream.device().get()
343 auto const warps =
static_cast<uint32_t
>(
349 auto const bytes = warps * heap_bytes;
350 CUDDL_TRY(allow_shared(detail::top_k_rows_kernel<false, false>, bytes));
351 CUDDL_TRY(allow_shared(detail::top_k_rows_kernel<true, false>, bytes));
352 CUDDL_TRY(allow_shared(detail::top_k_rows_kernel<false, true>, bytes));
353 CUDDL_TRY(allow_shared(detail::top_k_rows_kernel<true, true>, bytes));
354 CUDDL_TRY(allow_shared(detail::top_k_columns_kernel, bytes));
355 auto const slots =
static_cast<size_t>(genome_count) *
k;
363 cuda::make_device_buffer<uint64_t>(stream, stream.device(), slots, uint64_t{0U}),
364 cuda::make_device_buffer<uint64_t>(stream, stream.device(), slots, cuda::no_init),
365 cuda::make_device_buffer<uint32_t>(stream, stream.device(), 0U, cuda::no_init)
373 if (tile.reference_count != reference_count_) {
376 if (tile.query_count > genome_count_ || tile.first_query_id < first_genome_id_ ||
377 tile.first_query_id - first_genome_id_ > genome_count_ - tile.query_count) {
380 if (tile.word_offsets !=
nullptr) {
383 if (tile.upper_triangle && (first_genome_id_ != 0U || genome_count_ != reference_count_)) {
388 if (tile.query_count == 0U || reference_count_ == 0U)
return Ok();
390 auto const thresholded = tile.pass_bits !=
nullptr;
392 auto const rows = tile.upper_triangle
393 ? (thresholded ? detail::top_k_rows_kernel<true, true>
394 : detail::top_k_rows_kernel<false, true>)
395 : (thresholded ? detail::top_k_rows_kernel<true, false>
396 : detail::top_k_rows_kernel<false, false>);
397 auto const shared = warps_ * heap_bytes;
398 rows<<<(tile.query_count + warps_ - 1U) / warps_, warps_ * 32U, shared, stream.get()>>>(
402 if (!tile.upper_triangle)
return Ok();
404 auto const first_genome = tile.first_query_id + 1U;
405 if (first_genome >= reference_count_)
return Ok();
406 auto const first_word = first_genome / 32U;
407 auto const row_words = (tile.query_count + 31U) / 32U;
408 auto const needed =
static_cast<size_t>(row_words) * reference_count_;
409 if (columns_.size() < needed) {
411 cuda::make_device_buffer<uint32_t>(stream, stream.device(), needed, cuda::no_init)
415 auto const bit_blocks =
416 static_cast<uint64_t
>(row_words) * (tile.words_per_query() - first_word);
418 (thresholded ? detail::top_k_transpose_kernel<true>
419 : detail::top_k_transpose_kernel<false>)<<<
420 static_cast<uint32_t
>((bit_blocks + warps_per_block - 1U) / warps_per_block),
423 stream.get()>>>(tile, columns_.data(), first_word);
425 auto const genomes = reference_count_ - first_genome;
426 detail::top_k_columns_kernel<<<
427 (genomes + warps_ - 1U) / warps_,
430 stream.get()>>>(tile, slots, columns_.data(), first_genome);
438 auto const slots = keys_.size();
439 std::vector<uint64_t> keys(slots),
counts(slots);
443 cuda::make_device_buffer<uint64_t>(stream, stream.device(), slots, cuda::no_init)
446 cuda::make_device_buffer<uint64_t>(stream, stream.device(), slots, cuda::no_init)
448 auto const offsets = cuda::make_transform_iterator(
452 cub::DeviceSegmentedSort::SortPairsDescending(
456 sorted_counts.data(),
457 static_cast<int64_t
>(slots),
458 static_cast<int64_t
>(genome_count_),
464 CUDDL_CUDA_TRY(cuda::copy_bytes(stream, sorted_keys, cuda::std::span{keys}));
468 std::vector<batch_search_result> rows;
470 for (
size_t slot = 0U; slot < slots; ++slot) {
471 if (keys[slot] == 0U)
continue;
473 .query_id = first_genome_id_ +
static_cast<uint32_t
>(slot / k_),
474 .reference_id = ~
static_cast<uint32_t
>(keys[slot]),
481 [[nodiscard]] uint32_t
k() const noexcept {
487 uint32_t genome_count,
488 uint32_t reference_count,
489 uint32_t bucket_count,
491 uint32_t first_genome_id,
493 cuda::device_buffer<uint64_t> keys,
494 cuda::device_buffer<uint64_t>
counts,
495 cuda::device_buffer<uint32_t> columns
497 : genome_count_(genome_count),
498 reference_count_(reference_count),
499 bucket_count_(bucket_count),
501 first_genome_id_(first_genome_id),
503 keys_(std::move(keys)),
504 counts_(std::move(
counts)),
505 columns_(std::move(columns)) {}
509 template <
typename Kernel>
510 [[nodiscard]]
static Result<void> allow_shared(Kernel kernel,
size_t bytes) {
511 return cuda_try(cudaFuncSetAttribute(
512 kernel, cudaFuncAttributeMaxDynamicSharedMemorySize,
static_cast<int>(bytes)
516 uint32_t genome_count_;
517 uint32_t reference_count_;
518 uint32_t bucket_count_;
520 uint32_t first_genome_id_;
523 cuda::device_buffer<uint64_t> keys_, counts_;
525 cuda::device_buffer<uint32_t> columns_;
532[[nodiscard]]
inline std::vector<batch_search_result>
539 std::vector<batch_search_result> kept;
540 for (
size_t first = 0U; first < rows.size();) {
542 while (last < rows.size() && rows[last].query_id == rows[first].query_id) ++last;
545 rows.begin() +
static_cast<std::ptrdiff_t
>(first),
546 rows.begin() +
static_cast<std::ptrdiff_t
>(std::min<size_t>(last, first + k))
Keeps each genome's k most similar genomes from search tiles on the device.
Result< void > add(batch_result_tile const &tile, cuda::stream_ref stream)
Merges tile's passing pairs into the running neighbours, on stream.
uint32_t k() const noexcept
Result< std::vector< batch_search_result > > download(cuda::stream_ref stream) const
Each genome's neighbours, best first, genome by genome; waits for stream.
static Result< top_k_search > make(uint32_t genome_count, uint32_t reference_count, uint32_t bucket_count, uint32_t k, cuda::stream_ref stream, uint32_t first_genome_id=0U)
#define CUDDL_CUDA_TRY(...)
Propagates a CUDA error wrapped in cuddl::Result<void>.
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
constexpr uint32_t row_words
_Pragma("unroll") for(uint32_t bit=0U
constexpr uint32_t top_k_warps
Warps per merging block, each holding one heap; fewer when the heaps exceed shared memory.
top_k_warp_copy< false >(slots, row, heap)
constexpr size_t top_k_heap_words(uint32_t k) noexcept
Words of shared memory one heap takes: k keys, then their k packed counts.
__host__ __device__ constexpr uint64_t top_k_key(pairwise_counts const &counts, uint32_t id) noexcept
Ranking key of a neighbour: its weighted k-mer identity above its complemented ID.
constexpr uint32_t block_size
Threads per CTA for the single-pair and cardinality reduction kernels.
__global__ uint32_t * columns
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t tile
__global__ top_k_slots slots
__device__ void top_k_warp_copy(top_k_slots const &slots, size_t row, uint64_t *heap)
Copies the k entries of global heap row into, or for ToShared false out of, the shared heap heap with...
__device__ packed_pairwise_counts top_k_stored(batch_result_tile const &tile, uint32_t query, uint32_t reference)
The stored result of tile row query and reference, which must pass.
__global__ uint32_t uint32_t first_word
__device__ void top_k_warp_merge(uint64_t *heap, uint32_t k, uint32_t words, Bits bits_of, Entry entry_of)
One warp merges the candidates of words bitmap words into the heap of k entries at heap,...
__device__ uint32_t top_k_nth_bit(uint32_t mask, uint32_t n)
Position of set bit n, counting from zero, of mask, which has more than n set bits.
__device__ void top_k_replace_root(uint64_t *heap, uint32_t k, uint64_t key, uint64_t value)
Replaces the root of a min-heap of k keys at heap, whose values follow the keys, with key and value,...
::cuda::__ensure_current_context device_scope
Makes stream's device current for the enclosing scope and restores the previous context on exit.
__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.
for(auto i=static_cast< size_t >(blockIdx.x) *block_size+threadIdx.x;i< total;i+=static_cast< size_t >(gridDim.x) *block_size)
__device__ uint32_t top_k_word_bits(batch_result_tile const &tile, uint32_t query, uint32_t word, uint32_t words)
The pass bits of word word of tile row query: the stored bits, or for exhaustive tiles every referenc...
top_k_warp_copy< true >(slots, row, heap)
__global__ size_t uint16_t * rows
Batch operations over contiguous device rows.
@ counts
One 24-byte batch_search_result per pair: query ID, reference ID and the lower, equal,...
Result< void > Ok() noexcept
Success return for Result<void>; same as return {}.
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
__host__ __device__ constexpr float weighted_kmer_identity(pairwise_counts const &counts) noexcept
Weighted k-mer identity of a pair, equal / max(equal + min(lower, higher), 6), as sketch::wkid comput...
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.
std::vector< batch_search_result > select_top_k(std::vector< batch_search_result > rows, uint32_t k)
The k best rows of each query among rows, as top_k_search ranks them, best first and query by query.
static Error invalid_argument(std::string message)
Fallible API result: cuda::std::expected<T, Error> with cuDDL factories.
Non-owning view of one batch-search tile's results.
Stable query/reference IDs and their exact query-relative bucket comparison counts.
Start of genome row i of a collector's heaps, rows of k slots.
__host__ __device__ int64_t operator()(int64_t i) const noexcept
Each genome's k best neighbours so far: keys and packed counts in parallel rows of k,...
One pair's exact bucket comparison counts in 64 bits, the storage and transfer form of batch-search r...
__host__ __device__ constexpr pairwise_counts unpack(uint32_t bucket_count) const noexcept
Raw per-bucket classification counts for one sketch pair comparison.