cuDDL
Loading...
Searching...
No Matches
Classes | Public Member Functions | Static Public Attributes | List of all members
cuddl::detail::database_stager< K, BucketCount, Layout > Class Template Reference

Encodes genome records into register rows through a device arena. More...

Public Member Functions

 database_stager (cuda::stream_ref stream, staging_plan bounds, reference_build_statistics *statistics=nullptr, bool direct=false, cuda::std::optional< blacklist_view > blacklist=cuda::std::nullopt)
 
 ~database_stager ()
 Frees the arenas only after every copy into them.
 
 database_stager (database_stager const &)=delete
 
database_stager & operator= (database_stager const &)=delete
 
Result< void > add_genome (size_t genome, std::span< fastx_sequence_extent const > records, char const *pinned_base=nullptr, size_t pinned_size=0, std::unique_ptr< fastx_sequence_file > holder={})
 Stages one genome.
 
Result< void > add_packed_genome (size_t genome, std::unique_ptr< fastx_sequence_file > holder, char const *pinned_base=nullptr, size_t pinned_size=0)
 Stages one genome that pack_fastx_sequence_file packed into two-bit runs.
 
Result< void > add_resident (size_t genome, char const *bases, size_t size)
 Stages one genome's bases that already sit on the device, without a copy.
 
Result< void > begin_group (size_t genomes)
 Clears the register rows of the next group of genomes.
 
Result< device_span< uint32_t const > > end_group (size_t genomes)
 Launches everything staged and returns the group's rows on the device.
 
Result< void > finish ()
 Waits for the device and publishes the statistics.
 
Result< void > flush ()
 One launch for everything staged so far.
 
void wait_hold_slot ()
 Waits until the next genome staged by copy can be held without blocking.
 

Static Public Attributes

static constexpr size_t hold_slots = stager_hold_slots
 

Detailed Description

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
class cuddl::detail::database_stager< K, BucketCount, Layout >

Encodes genome records into register rows through a device arena.

One copy of the staging machinery, fed by every entry point that builds a reference database from bases. A producer supplies, per genome, the spans of its records: one span per record, bases only, no line breaks, and no k-mer crossing two spans.

The arena, its descriptors, the record runs, the k - 1 overlap a record too large for the arena needs and the batch launches all live here. Rows stay on the device: each group's rows go to the caller's sink, which copies them wherever the build wants them. A producer keeps only what is its own: where the bytes come from, and which of them must outlive the copies the device makes from them.

Definition at line 222 of file database_staging.cuh.

Constructor & Destructor Documentation

◆ database_stager() [1/2]

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
cuddl::detail::database_stager< K, BucketCount, Layout >::database_stager ( cuda::stream_ref  stream,
staging_plan  bounds,
reference_build_statistics *  statistics = nullptr,
bool  direct = false,
cuda::std::optional< blacklist_view >  blacklist = cuda::std::nullopt 
)
inline
Exceptions
cuda::cuda_erroror std::bad_alloc when the device buffers cannot be allocated.
Parameters
streamStream owning the arena, descriptors, and row store.
boundsArena sizing the device row store, arena, and descriptors.
statisticsOptional build statistics sink, null to skip.
directstages the caller's bytes in place, for a device that reads pageable host memory: no copy, and the arena stays unused.

Definition at line 256 of file database_staging.cuh.

263 : stream_(stream),
264 bounds_(bounds),
265 statistics_(statistics),
266 direct_(direct),
267 blacklist_(blacklist),
268 rows_(
269 cuda::make_device_buffer<uint32_t>(
270 stream,
271 stream.device(),
272 bounds.group * BucketCount,
273 cuda::no_init
274 )
275 ),
276 copy_stream_(stream.device()),
277 kernel_stream_(stream.device(), greatest_stream_priority()),
278 // The copy stream fills one arena while kernels read the others.
279 arena_(
280 cuda::make_device_buffer<char>(
281 stream,
282 stream.device(),
283 direct ? bounds.staging : stager_arenas * arena_stride(bounds.staging),
284 cuda::no_init
285 )
286 ),
287 descriptors_(
288 cuda::make_device_buffer<sequence_batch_chunk>(
289 stream,
290 stream.device(),
291 bounds.max_pieces,
292 cuda::no_init
293 )
294 ),
295 held_(hold_slots),
296 sm_(static_cast<size_t>(
297 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
298 )),
299 max_grid_(
300 static_cast<size_t>(
301 stream.device().attribute(cuda::device_attributes::max_grid_dim_x)
302 )
303 ) {
304 held_consumed_.reserve(hold_slots);
305 for (size_t slot = 0; slot < hold_slots; ++slot) {
306 held_consumed_.emplace_back(stream);
307 }
308 staged_chunks_.reserve(std::min<size_t>(bounds_.max_pieces, size_t{1} << 16));
309 copied_chunks_.reserve(staged_chunks_.capacity());
310 // The arenas are allocated in order on the main stream.
311 copy_stream_.wait(stream_);
312 }
static constexpr size_t hold_slots
constexpr size_t stager_arenas
Staging arenas the copy stream rotates through, so copies run ahead of the kernels that read earlier ...
__global__ size_t uint32_t bool blacklist_view blacklist
Definition kernels.cuh:96

◆ ~database_stager()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
cuddl::detail::database_stager< K, BucketCount, Layout >::~database_stager ( )
inline

Frees the arenas only after every copy into them.

Definition at line 314 of file database_staging.cuh.

314 {
315 CUDDL_CUDA_ABORT(cudaEventRecord(copies_done_.get(), copy_stream_.get()));
316 CUDDL_CUDA_ABORT(cudaStreamWaitEvent(stream_.get(), copies_done_.get()));
317 }
#define CUDDL_CUDA_ABORT(expr)
Checks a CUDA call and aborts on failure (destructors and RAII only).
Definition error.hpp:245

◆ database_stager() [2/2]

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
cuddl::detail::database_stager< K, BucketCount, Layout >::database_stager ( database_stager< K, BucketCount, Layout > const &  )
delete

Member Function Documentation

◆ add_genome()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::database_stager< K, BucketCount, Layout >::add_genome ( size_t  genome,
std::span< fastx_sequence_extent const >  records,
char const *  pinned_base = nullptr,
size_t  pinned_size = 0,
std::unique_ptr< fastx_sequence_file >  holder = {} 
)
inline

Stages one genome.

holder keeps its bytes alive until the device is done.

A genome with no records, or with records shorter than one window, keeps its row and records no descriptor. pinned_base names the page-locked buffer the bytes came from, when the producer has one, so the statistics can say which transfer path was taken.

Definition at line 326 of file database_staging.cuh.

331 {}
332 ) {
333 auto const record_size = [&](size_t index) {
334 return static_cast<size_t>(records[index].end - records[index].begin);
335 };
336 size_t record = 0;
337 while (record < records.size()) {
338 // Staged in place, the arena is not the limit; the descriptors and the piece size are.
339 auto const room =
340 direct_ ? std::numeric_limits<size_t>::max() : bounds_.staging - arena_used_;
341 size_t run = record;
342 while (run < records.size()) {
343 auto const run_bytes =
344 static_cast<size_t>(records[run].end - records[record].begin);
345 if (run_bytes > room || record_size(run) > bounds_.max_piece ||
346 staged_chunks_.size() + (run - record) >= bounds_.max_pieces) {
347 break;
348 }
349 ++run;
350 }
351 if (run == record) {
352 if (arena_used_ != 0) {
353 CUDDL_TRY(flush());
354 continue;
355 }
356 // One record larger than the whole arena: split it, and give each piece the
357 // k - 1 bases of overlap its kmers need.
358 auto const size = record_size(record);
359 size_t offset = 0;
360 while (offset < size) {
361 auto const overlap = offset == 0 ? size_t{0} : std::min(offset, size_t{K - 1});
362 auto const bases = std::min(size - offset, bounds_.max_piece);
363 auto const span = overlap + bases;
364 if (span < K) break;
365 if (arena_used_ + span > bounds_.staging ||
366 staged_chunks_.size() >= bounds_.max_pieces) {
367 CUDDL_TRY(flush());
368 }
369 auto const* const source = records[record].begin + offset - overlap;
370 CUDDL_TRY(stage(
371 source,
372 span,
373 genome,
374 static_cast<uint32_t>(span - K + 1),
375 pinned_base,
376 pinned_size
377 ));
378 offset += bases;
379 }
380 ++record;
381 continue;
382 }
383 // Copy the run in one transfer, then describe each record inside it. Staged in
384 // place, the records keep their own addresses and there is nothing to copy.
385 auto const* const source = records[record].begin;
386 auto const run_bytes = static_cast<size_t>(records[run - 1].end - source);
387 account(source, run_bytes, pinned_base, pinned_size);
388 if (!direct_) {
390 cuda::copy_bytes(
391 copy_stream_,
392 cuda::std::span{source, run_bytes},
393 device_span<char>{arena() + arena_used_, run_bytes}
394 )
395 );
396 }
397 for (size_t index = record; index < run; ++index) {
398 auto const size = record_size(index);
399 if (size < K) continue; // no window to record
400 auto const* const bases =
401 direct_ ? records[index].begin
402 : arena() + arena_used_ +
403 static_cast<size_t>(records[index].begin - source);
404 describe(bases, static_cast<uint32_t>(size - K + 1), genome);
405 }
406 if (direct_) {
407 // Staged in place the arena never fills, so the bytes held for the device are
408 // what bounds a batch. Half the arena, because a batch is still held while the
409 // next one fills: the two together then cost what the copies would have.
410 held_bytes_ += run_bytes;
411 if (held_bytes_ >= bounds_.staging / 2) {
412 CUDDL_TRY(flush());
413 }
414 } else {
415 arena_used_ += run_bytes;
416 ++transfers_;
417 }
418 record = run;
419 }
420 if (holder) hold(std::move(holder));
421 return Ok();
422 }
Result< void > flush()
One launch for everything staged so far.
#define CUDDL_CUDA_TRY(...)
Propagates a CUDA error wrapped in cuddl::Result<void>.
Definition error.hpp:248
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:225
Result< void > Ok() noexcept
Success return for Result<void>; same as return {}.
Definition error.hpp:181

◆ add_packed_genome()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::database_stager< K, BucketCount, Layout >::add_packed_genome ( size_t  genome,
std::unique_ptr< fastx_sequence_file >  holder,
char const *  pinned_base = nullptr,
size_t  pinned_size = 0 
)
inline

Stages one genome that pack_fastx_sequence_file packed into two-bit runs.

Runs keep their words together. One too large for a batch splits into pieces of whole words whose window counts are multiples of sixteen, so every piece starts on a word.

Definition at line 428 of file database_staging.cuh.

433 {
434 constexpr size_t word_bases = 16;
435 auto const& file = *holder;
436 auto const& runs = file.packed;
437 auto const words = [&](size_t index) {
438 return (runs[index].bases + word_bases - 1) / word_bases;
439 };
440 size_t run = 0;
441 while (run < runs.size()) {
442 auto const room = direct_ ? std::numeric_limits<size_t>::max() : arena_room_words();
443 size_t last = run;
444 while (last < runs.size()) {
445 auto const span_words = runs[last].word + words(last) - runs[run].word;
446 if (span_words > room || words(last) * sizeof(uint32_t) > bounds_.max_piece ||
447 staged_chunks_.size() + (last - run) >= bounds_.max_pieces) {
448 break;
449 }
450 ++last;
451 }
452 if (last == run) {
453 if (arena_used_ != 0 || !staged_chunks_.empty()) {
454 CUDDL_TRY(flush());
455 continue;
456 }
457 auto const halo = (size_t{K} - 1 + word_bases - 1) / word_bases;
458 auto const capacity =
459 std::min(bounds_.max_piece, bounds_.staging) / sizeof(uint32_t);
460 if (capacity <= halo) {
461 return Err(Error::resource("staging arena too small for a packed run"));
462 }
463 auto const step = (capacity - halo) * word_bases;
464 auto const total = runs[run].bases - K + 1;
465 for (size_t start = 0; start < total; start += step) {
466 auto const windows = std::min(step, total - start);
467 auto const first = runs[run].word + start / word_bases;
468 auto const count = (start + windows + K - 1 + word_bases - 1) / word_bases -
469 start / word_bases;
470 if ((!direct_ && count > arena_room_words()) ||
471 staged_chunks_.size() >= bounds_.max_pieces) {
472 CUDDL_TRY(flush());
473 }
474 auto const* const bases =
475 CUDDL_TRY(place_words(file.words + first, count, pinned_base, pinned_size));
476 describe(bases, static_cast<uint32_t>(windows), genome, true);
477 }
478 ++run;
479 continue;
480 }
481 auto const first = runs[run].word;
482 auto const count = runs[last - 1].word + words(last - 1) - first;
483 auto const* const base =
484 CUDDL_TRY(place_words(file.words + first, count, pinned_base, pinned_size));
485 for (size_t index = run; index < last; ++index) {
486 describe(
487 base + (runs[index].word - first) * sizeof(uint32_t),
488 static_cast<uint32_t>(runs[index].bases - K + 1),
489 genome,
490 true
491 );
492 }
493 if (direct_ && held_bytes_ >= bounds_.staging / 2) CUDDL_TRY(flush());
494 run = last;
495 }
496 hold(std::move(holder));
497 return Ok();
498 }
auto const count
Definition kernels.cuh:1031
auto const total
Definition kernels.cuh:443
auto const words
Definition top_k.cuh:204
auto const word
Definition top_k.cuh:241
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:176
static Error resource(std::string message)
Definition error.hpp:103
Here is the call graph for this function:

◆ add_resident()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::database_stager< K, BucketCount, Layout >::add_resident ( size_t  genome,
char const *  bases,
size_t  size 
)
inline

Stages one genome's bases that already sit on the device, without a copy.

The bytes must stay valid until the batch that reads them runs: through the next flush, whose kernel the caller then orders its reuse of them after.

Definition at line 504 of file database_staging.cuh.

504 {
505 if (size < K) return Ok();
506 if (staged_chunks_.size() >= bounds_.max_pieces) CUDDL_TRY(flush());
507 describe(bases, static_cast<uint32_t>(size - K + 1), genome);
508 return Ok();
509 }
Here is the call graph for this function:

◆ begin_group()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::database_stager< K, BucketCount, Layout >::begin_group ( size_t  genomes)
inline

Clears the register rows of the next group of genomes.

Definition at line 512 of file database_staging.cuh.

512 {
513 auto const resident = CUDDL_TRY(begin_resident(stream_));
515 cuda::fill_bytes(
516 stream_, cuda::std::span{rows_.data(), genomes * BucketCount}, uint32_t{0}
517 )
518 );
519 CUDDL_TRY(end_resident(resident, stream_));
520 return Ok();
521 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ end_group()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< device_span< uint32_t const > > cuddl::detail::database_stager< K, BucketCount, Layout >::end_group ( size_t  genomes)
inline

Launches everything staged and returns the group's rows on the device.

Each genome's row holds BucketCount registers, the layout a single sketch allocation has. The rows are complete once the stream reaches this point, and the next begin_group reuses them, so a consumer enqueues its reads on the stager's stream before then.

Definition at line 529 of file database_staging.cuh.

529 {
530 CUDDL_TRY(flush());
531 return device_span<uint32_t const>{rows_.data(), genomes * BucketCount};
532 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ finish()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::database_stager< K, BucketCount, Layout >::finish ( )
inline

Waits for the device and publishes the statistics.

Definition at line 535 of file database_staging.cuh.

535 {
536 CUDDL_CUDA_TRY(stream_.sync());
537 CUDDL_CUDA_TRY(copy_stream_.sync());
538 if (statistics_ != nullptr) {
539 statistics_->staging_bytes = bounds_.staging;
540 statistics_->batches = batches_;
541 statistics_->transfers = transfers_;
542 for (auto const& span : resident_spans_) {
543 float elapsed_ms = 0;
544 CUDDL_CUDA_TRY(cudaEventElapsedTime(&elapsed_ms, span.start, span.stop));
545 statistics_->resident_compute_ms += elapsed_ms;
546 }
547 }
548 return Ok();
549 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ flush()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::database_stager< K, BucketCount, Layout >::flush ( )
inline

One launch for everything staged so far.

Copies queued after it land in the arena only once the device has finished reading it, so no host wait is needed beyond the descriptor copy's own.

Definition at line 669 of file database_staging.cuh.

669 {
670 if (staged_chunks_.empty()) {
671 // Records shorter than k stage bytes but record no window, so a flush can arrive
672 // with nothing to launch. The offsets still have to reset, or a caller that flushed
673 // to make room would ask again for the same room.
674 arena_used_ = 0;
675 block_end_ = 0;
676 return Ok();
677 }
678 copied_consumed_.sync();
679 copied_chunks_.swap(staged_chunks_);
680 // The sketch kernel runs ahead of lower-priority work sharing the device, such as
681 // nvCOMP inflation: the copies that feed the next batch wait on it.
682 queued_.record(stream_);
683 kernel_stream_.wait(queued_);
684 if (!direct_) {
685 copies_done_.record(copy_stream_);
686 kernel_stream_.wait(copies_done_);
687 }
689 cuda::copy_bytes(
690 kernel_stream_,
691 cuda::std::span{copied_chunks_.data(), copied_chunks_.size()},
692 device_span<sequence_batch_chunk>{descriptors_.data(), copied_chunks_.size()}
693 )
694 );
695 copied_consumed_.record(kernel_stream_);
696 auto const grid = std::min(block_end_, max_grid_);
697 if (grid != 0) {
698 auto const resident = CUDDL_TRY(begin_resident(kernel_stream_));
699 // The kernel keeps the default carveout: a 50% L1 split sped up filtered sketching on
700 // an RTX 5090, but made `gpu`-backend builds 2.6 times slower beside nvCOMP's kernels.
701 auto launch = [&]<bool Filter, bool Naive>() {
702 add_sequence_batch_kernel<BucketCount, Layout, Filter, Naive>
703 <<<static_cast<uint32_t>(grid), 256, 0, kernel_stream_.get()>>>(
704 descriptors_.data(),
705 copied_chunks_.size(),
706 block_end_,
707 K,
708 rows_.data(),
709 blacklist_.value_or(blacklist_view{})
710 );
711 };
712 auto const encode = [&]<bool Naive>() {
713 if (blacklist_) {
714 launch.template operator()<true, Naive>();
715 } else {
716 launch.template operator()<false, Naive>();
717 }
718 };
719 // Only ablation builds compile the naive encoder.
720 if constexpr (ablations_enabled) {
721 if (ablated("kmer-encoding")) {
722 encode.template operator()<true>();
723 } else {
724 encode.template operator()<false>();
725 }
726 } else {
727 encode.template operator()<false>();
728 }
729 CUDDL_CUDA_TRY(cudaGetLastError());
730 CUDDL_TRY(end_resident(resident, kernel_stream_));
731 }
732 kernel_done_.record(kernel_stream_);
733 stream_.wait(kernel_done_);
734 if (direct_ && !batch_files_.empty()) {
735 // The launch above is what reads those buffers, so they wait on this batch's event.
736 released_.push_back({cuda::event{stream_}, std::move(batch_files_)});
737 batch_files_.clear();
738 released_.back().first.record(stream_);
739 while (released_.size() > held_batches) {
740 released_.front().first.sync();
741 released_.pop_front();
742 }
743 }
744 if (!direct_) {
745 // The next batch fills the other arena once the kernel that last read it is done.
746 arena_done_[arena_index_].record(stream_);
747 arena_index_ = (arena_index_ + 1) % stager_arenas;
748 copy_stream_.wait(arena_done_[arena_index_]);
749 }
750 ++batches_;
751 staged_chunks_.clear();
752 arena_used_ = 0;
753 held_bytes_ = 0;
754 block_end_ = 0;
755 return Ok();
756 }
constexpr bool ablations_enabled
Definition ablation.hpp:79
constexpr bool ablated(std::string_view) noexcept
Always false: this build compiles the ablation switches out.
Definition ablation.hpp:82
Here is the call graph for this function:
Here is the caller graph for this function:

◆ operator=()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
database_stager & cuddl::detail::database_stager< K, BucketCount, Layout >::operator= ( database_stager< K, BucketCount, Layout > const &  )
delete

◆ wait_hold_slot()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
void cuddl::detail::database_stager< K, BucketCount, Layout >::wait_hold_slot ( )
inline

Waits until the next genome staged by copy can be held without blocking.

The thread that stages copied genomes may call this outside any lock it shares with other staging threads, so waiting on the transfers does not hold them up.

Definition at line 762 of file database_staging.cuh.

762 {
763 if (direct_) return;
764 auto const slot = held_count_ % held_.size();
765 if (held_[slot]) held_consumed_[slot].sync();
766 }

Member Data Documentation

◆ hold_slots

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
constexpr size_t cuddl::detail::database_stager< K, BucketCount, Layout >::hold_slots = stager_hold_slots
staticconstexpr

Definition at line 248 of file database_staging.cuh.


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