cuDDL
Loading...
Searching...
No Matches
Public Member Functions | Static Public Member Functions | List of all members
cuddl::top_k_search Class Reference

Keeps each genome's k most similar genomes from search tiles on the device. More...

Public Member Functions

Result< void > add (batch_result_tile const &tile, cuda::stream_ref stream)
 Merges tile's passing pairs into the running neighbours, on stream.
 
Result< std::vector< batch_search_result > > download (cuda::stream_ref stream) const
 Each genome's neighbours, best first, genome by genome; waits for stream.
 
uint32_t k () const noexcept
 

Static Public Member Functions

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)
 

Detailed Description

Keeps each genome's k most similar genomes from search tiles on the device.

Pass add as the tile consumer of search_batch_async or search_all_to_all_async, for either the exhaustive or the indexed search, in place of downloading every passing pair. A genome's neighbours rank by weighted k-mer identity, the smaller ID first on a tie; a genome with fewer than k passing pairs keeps all of them.

Batch searches keep each query's best references, and all-to-all searches each database row's best other rows, in both directions of the stored upper triangle. Each genome keeps a min-heap of its k best. Per tile, one warp per query row merges the row's passing pairs into its genome's heap; for all-to-all tiles the pass bits are then transposed, and one warp per later genome merges its column of mirrored pairs. Each merging warp holds its genome's heap in shared memory and alone updates it, so no update takes a lock, and the work follows the passing pairs.

A collector sees only the tiles given to it. Replicas sharing an all-to-all tile queue each keep their own, and select_top_k merges their downloads.

Definition at line 317 of file top_k.cuh.

Member Function Documentation

◆ add()

Result< void > cuddl::top_k_search::add ( batch_result_tile const &  tile,
cuda::stream_ref  stream 
)
inline

Merges tile's passing pairs into the running neighbours, on stream.

The tile is one a search passes to its consumer, with results in their device slots.

Definition at line 371 of file top_k.cuh.

371 {
372 detail::device_scope const scope{stream};
373 if (tile.reference_count != reference_count_) {
374 return Err(Error::invalid_argument("tile belongs to a database of another size"));
375 }
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) {
378 return Err(Error::invalid_argument("tile rows lie outside the collected genomes"));
379 }
380 if (tile.word_offsets != nullptr) {
381 return Err(Error::invalid_argument("top-k search takes device tiles, not host copies"));
382 }
383 if (tile.upper_triangle && (first_genome_id_ != 0U || genome_count_ != reference_count_)) {
384 return Err(
385 Error::invalid_argument("an all-to-all tile needs a collector of every row")
386 );
387 }
388 if (tile.query_count == 0U || reference_count_ == 0U) return Ok();
389 detail::top_k_slots const slots{keys_.data(), counts_.data(), k_, first_genome_id_};
390 auto const thresholded = tile.pass_bits != nullptr;
391 auto const heap_bytes = detail::top_k_heap_words(k_) * sizeof(uint64_t);
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()>>>(
399 tile, slots
400 );
401 CUDDL_CUDA_TRY(cudaGetLastError());
402 if (!tile.upper_triangle) return Ok();
403 // Genomes after the tile's first row take its rows before them as candidates.
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) {
410 columns_ = CUDDL_CUDA_TRY(
411 cuda::make_device_buffer<uint32_t>(stream, stream.device(), needed, cuda::no_init)
412 );
413 }
414 // One warp per 32-by-32 block of the pass bits from the first genome's word on.
415 auto const bit_blocks =
416 static_cast<uint64_t>(row_words) * (tile.words_per_query() - first_word);
417 auto const warps_per_block = detail::block_size / 32U;
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),
422 0,
423 stream.get()>>>(tile, columns_.data(), first_word);
424 CUDDL_CUDA_TRY(cudaGetLastError());
425 auto const genomes = reference_count_ - first_genome;
426 detail::top_k_columns_kernel<<<
427 (genomes + warps_ - 1U) / warps_,
428 warps_ * 32U,
429 shared,
430 stream.get()>>>(tile, slots, columns_.data(), first_genome);
431 CUDDL_CUDA_TRY(cudaGetLastError());
432 return Ok();
433 }
#define CUDDL_CUDA_TRY(...)
Propagates a CUDA error wrapped in cuddl::Result<void>.
Definition error.hpp:248
constexpr uint32_t row_words
Definition kernels.cuh:1546
auto const thresholded
Definition kernels.cuh:1888
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.
Definition top_k.cuh:56
constexpr uint32_t block_size
Threads per CTA for the single-pair and cardinality reduction kernels.
Definition kernels.cuh:28
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t tile
Definition kernels.cuh:922
__global__ top_k_slots slots
Definition top_k.cuh:195
constexpr uint32_t warps_per_block
Definition kernels.cuh:935
__global__ uint32_t uint32_t first_word
Definition top_k.cuh:234
::cuda::__ensure_current_context device_scope
Makes stream's device current for the enclosing scope and restores the previous context on exit.
__global__ size_t uint16_t * rows
Definition kernels.cuh:1499
Result< void > Ok() noexcept
Success return for Result<void>; same as return {}.
Definition error.hpp:181
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:176
static Error invalid_argument(std::string message)
Definition error.hpp:99
Here is the call graph for this function:

◆ download()

Result< std::vector< batch_search_result > > cuddl::top_k_search::download ( cuda::stream_ref  stream) const
inline

Each genome's neighbours, best first, genome by genome; waits for stream.

Definition at line 436 of file top_k.cuh.

436 {
437 detail::device_scope const scope{stream};
438 auto const slots = keys_.size();
439 std::vector<uint64_t> keys(slots), counts(slots);
440 if (slots != 0U) {
441 // Rows are heaps on the device; sorting them there leaves the host a linear pass.
443 cuda::make_device_buffer<uint64_t>(stream, stream.device(), slots, cuda::no_init)
444 );
445 auto sorted_counts = CUDDL_CUDA_TRY(
446 cuda::make_device_buffer<uint64_t>(stream, stream.device(), slots, cuda::no_init)
447 );
448 auto const offsets = cuda::make_transform_iterator(
449 cuda::counting_iterator<int64_t>{0}, detail::top_k_row_offset{k_}
450 );
452 cub::DeviceSegmentedSort::SortPairsDescending(
453 keys_.data(),
454 sorted_keys.data(),
455 counts_.data(),
456 sorted_counts.data(),
457 static_cast<int64_t>(slots),
458 static_cast<int64_t>(genome_count_),
459 offsets,
460 offsets + 1,
461 stream
462 )
463 );
464 CUDDL_CUDA_TRY(cuda::copy_bytes(stream, sorted_keys, cuda::std::span{keys}));
465 CUDDL_CUDA_TRY(cuda::copy_bytes(stream, sorted_counts, cuda::std::span{counts}));
466 }
467 CUDDL_CUDA_TRY(stream.sync());
468 std::vector<batch_search_result> rows;
469 rows.reserve(slots);
470 for (size_t slot = 0U; slot < slots; ++slot) {
471 if (keys[slot] == 0U) continue;
472 rows.push_back({
473 .query_id = first_genome_id_ + static_cast<uint32_t>(slot / k_),
474 .reference_id = ~static_cast<uint32_t>(keys[slot]),
475 .counts = packed_pairwise_counts{counts[slot]}.unpack(bucket_count_),
476 });
477 }
478 return rows;
479 }
__global__ uint16_t const * sorted_keys
Definition kernels.cuh:469
__global__ uint32_t uint32_t uint64_t * keys
static __global__ uint32_t const * offsets
Definition kernels.cuh:536
@ counts
One 24-byte batch_search_result per pair: query ID, reference ID and the lower, equal,...
Here is the call graph for this function:

◆ k()

uint32_t cuddl::top_k_search::k ( ) const
inlinenoexcept

Definition at line 481 of file top_k.cuh.

481 {
482 return k_;
483 }
Here is the caller graph for this function:

◆ make()

static Result< top_k_search > cuddl::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 
)
inlinestatic
Parameters
genome_countQueries of a batch search, or rows of an all-to-all search.
reference_countDatabase rows, the length of every result row.
bucket_countBuckets per sketch, from which downloads recover both-empty counts.
first_genome_idID of the first query of a batch search; zero for all-to-all.

Blocks use as many warps as their heaps fit the shared memory stream's device gives a block. Fails when one heap does not fit.

Definition at line 328 of file top_k.cuh.

335 {
336 if (k == 0U) return Err(Error::invalid_argument("top-k search needs k of at least 1"));
337 detail::device_scope const scope{stream};
338 int shared_limit = 0;
339 CUDDL_CUDA_TRY(cudaDeviceGetAttribute(
340 &shared_limit, cudaDevAttrMaxSharedMemoryPerBlockOptin, stream.device().get()
341 ));
342 auto const heap_bytes = detail::top_k_heap_words(k) * sizeof(uint64_t);
343 auto const warps = static_cast<uint32_t>(
344 std::min<size_t>(detail::top_k_warps, static_cast<size_t>(shared_limit) / heap_bytes)
345 );
346 if (warps == 0U) {
347 return Err(Error::invalid_argument("top-k heap exceeds this GPU's shared memory"));
348 }
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;
356 return CUDDL_CUDA_TRY(top_k_search(
357 genome_count,
358 reference_count,
359 bucket_count,
360 k,
361 first_genome_id,
362 warps,
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)
366 ));
367 }
uint32_t k() const noexcept
Definition top_k.cuh:481
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:225
constexpr uint32_t top_k_warps
Warps per merging block, each holding one heap; fewer when the heaps exceed shared memory.
Definition top_k.cuh:53
auto const warps
Definition kernels.cuh:2495
Here is the call graph for this function:

The documentation for this class was generated from the following file: