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

Host-owned reference sketches and labels, ready for binary storage or GPU upload. More...

Public Member Functions

kmer_blacklist const & blacklist () const noexcept
 
reference_database_metadata metadata () const noexcept
 Compatibility metadata and reference count.
 
std::span< std::string const > names () const noexcept
 Reference labels in reference-ID order.
 
std::span< uint16_t const > rows () const noexcept
 Winner scores in reference-ID order, one bucket row per genome.
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< reference_database< K, BucketCount, Layout > > upload (cuda::stream_ref stream) const
 Uploads reference score rows and labels without constructing an index.
 
Result< void > save (std::filesystem::path const &path) const
 Writes a versioned, little-endian, CRC32-checked binary file.
 

Static Public Member Functions

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
static Result< reference_database_file > build (std::span< std::filesystem::path const > paths, cuda::stream_ref stream, path_build_options options={})
 Builds one sketch per plain or gzip/BGZF FASTA/FASTQ file on the GPU.
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
static Result< reference_database_file > build_from_sequences (std::span< sequence_genome const > genomes, cuda::stream_ref stream, sequence_build_options options={})
 Builds one sketch per genome from bases the caller already holds.
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
static Result< reference_database_file > from_store (device_span< uint32_t const > store, std::span< std::string const > names, cuda::stream_ref stream, kmer_blacklist const &blacklist)
 Adopts a device store as the host rows this form saves.
 
static Result< reference_database_file > load (std::filesystem::path const &path)
 Reads and validates a binary database without requiring a GPU.
 

Friends

class reference_index_file
 
struct detail::reference_file_assembly
 

Detailed Description

Host-owned reference sketches and labels, ready for binary storage or GPU upload.

One input file is one genome, including all of its FASTA/FASTQ records. Input order defines stable reference IDs; labels preserve the supplied paths. Files store winner-score rows and compatibility metadata. Upload creates row storage, not an index. Build and upload are synchronous. The supplied stream must outlive the uploaded database.

Definition at line 267 of file reference_database_file.cuh.

Member Function Documentation

◆ blacklist()

kmer_blacklist const & cuddl::reference_database_file::blacklist ( ) const
inlinenoexcept

Definition at line 272 of file reference_database_file.cuh.

272 {
273 return blacklist_;
274 }
Here is the caller graph for this function:

◆ build()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
static Result< reference_database_file > cuddl::reference_database_file::build ( std::span< std::filesystem::path const >  paths,
cuda::stream_ref  stream,
path_build_options  options = {} 
)
inlinestatic

Builds one sketch per plain or gzip/BGZF FASTA/FASTQ file on the GPU.

K-mers never cross record boundaries or ambiguous bases. FASTQ qualities are validated and ignored. Loader threads compact and stage records into a device arena, and one batch kernel encodes every record in it, so the device is fed in large batches rather than slice by slice. Register rows stay on the device for a group of genomes and return to the host once per group. Empty genomes retain their IDs and have zero rows.

Definition at line 299 of file reference_database_file.cuh.

302 {}
303 ) try {
304 // NVCC 13.3 crashes on CUDDL_TRY directly inside a try block. Keep its GNU statement
305 // expressions in a separate lambda scope, outside the exception-catching function.
306 return [&]() -> Result<reference_database_file> {
307 using database_type = reference_database<K, BucketCount, Layout>;
308 if (paths.size() > std::numeric_limits<uint32_t>::max() ||
309 paths.size() >
310 std::numeric_limits<size_t>::max() / (BucketCount * sizeof(uint32_t))) {
311 return Err(Error::resource("reference collection exceeds database capacity"));
312 }
313 reference_database_file result;
314 result.metadata_ = {
315 score_compatibility::current<K, BucketCount, Layout>(),
316 static_cast<uint32_t>(paths.size())
317 };
318 CUDDL_TRY((result.set_blacklist<K, BucketCount>(options.blacklist, stream)));
319 result.rows_.resize(paths.size() * BucketCount);
320 result.names_.reserve(paths.size());
321 // No inputs means no staging budget to resolve and nothing to encode.
322 if (paths.empty()) return result;
323 CUDDL_TRY((detail::stage_paths<K, BucketCount, Layout>(
324 paths,
325 stream,
326 options.staging_bytes,
327 options.parser_workers,
328 options.transfer,
329 options.decompression,
330 options.direct_reads,
331 options.statistics,
332 result.download_rows<BucketCount>(stream),
333 options.blacklist ? options.blacklist->get().view() : cuda::std::nullopt
334 )));
335 for (auto const& path : paths) result.names_.push_back(path.string());
336 // Instantiate the same constraints as the destination GPU database.
337 static_assert(sizeof(database_type) > 0);
338 return result;
339 }();
340 } catch (cuda::cuda_error const& error) {
341 return Err(Error::cuda(static_cast<cudaError_t>(error.status())));
342 } catch (std::system_error const& error) {
343 return Err(Error::resource(error.what()));
344 } catch (std::bad_alloc const& error) {
345 return Err(Error::resource(error.what()));
346 }
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:225
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:176
static Error cuda(cudaError_t code, std::source_location location=std::source_location::current())
Definition error.hpp:90
static Error resource(std::string message)
Definition error.hpp:103

◆ build_from_sequences()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
static Result< reference_database_file > cuddl::reference_database_file::build_from_sequences ( std::span< sequence_genome const >  genomes,
cuda::stream_ref  stream,
sequence_build_options  options = {} 
)
inlinestatic

Builds one sketch per genome from bases the caller already holds.

Each genome supplies its records as spans of consecutive bases with the line breaks already removed, the shape a parsed FASTX record has. The caller keeps every byte alive until this returns, and the build is synchronous like build. Labels come from the genomes' names, and reference IDs follow the order of genomes.

Definition at line 357 of file reference_database_file.cuh.

360 {}
361 ) try {
362 return [&]() -> Result<reference_database_file> {
363 using database_type = reference_database<K, BucketCount, Layout>;
364 if (genomes.size() > std::numeric_limits<uint32_t>::max() ||
365 genomes.size() >
366 std::numeric_limits<size_t>::max() / (BucketCount * sizeof(uint32_t))) {
367 return Err(Error::resource("reference collection exceeds database capacity"));
368 }
369 reference_database_file result;
370 result.metadata_ = {
371 score_compatibility::current<K, BucketCount, Layout>(),
372 static_cast<uint32_t>(genomes.size())
373 };
374 CUDDL_TRY((result.set_blacklist<K, BucketCount>(options.blacklist, stream)));
375 result.rows_.resize(genomes.size() * BucketCount);
376 result.names_.reserve(genomes.size());
377 if (genomes.empty()) return result;
378 CUDDL_TRY((detail::stage_sequences<K, BucketCount, Layout>(
379 genomes,
380 stream,
381 options.staging_bytes,
382 options.statistics,
383 result.download_rows<BucketCount>(stream),
384 options.blacklist ? options.blacklist->get().view() : cuda::std::nullopt
385 )));
386 for (auto const& genome : genomes) result.names_.emplace_back(genome.name);
387 static_assert(sizeof(database_type) > 0);
388 return result;
389 }();
390 } catch (cuda::cuda_error const& error) {
391 return Err(Error::cuda(static_cast<cudaError_t>(error.status())));
392 } catch (std::system_error const& error) {
393 return Err(Error::resource(error.what()));
394 } catch (std::bad_alloc const& error) {
395 return Err(Error::resource(error.what()));
396 }

◆ from_store()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
static Result< reference_database_file > cuddl::reference_database_file::from_store ( device_span< uint32_t const >  store,
std::span< std::string const >  names,
cuda::stream_ref  stream,
kmer_blacklist const &  blacklist 
)
inlinestatic

Adopts a device store as the host rows this form saves.

A store holds BucketCount registers per reference, the layout a single sketch allocation has. A staged build and the streamed tile builder both write that layout.

names labels one reference each in store order, or is empty for a database without labels. The caller must supply the blacklist used to construct these registers, or an empty blacklist for unfiltered registers.

Definition at line 409 of file reference_database_file.cuh.

414 {
415 return [&]() -> Result<reference_database_file> {
416 if (store.size() % BucketCount != 0) {
417 return Err(Error::invalid_argument("a store must contain whole sketches"));
418 }
419 size_t const count = store.size() / BucketCount;
420 if (!names.empty() && names.size() != count) {
421 return Err(Error::invalid_argument("labels must be empty or name every reference"));
422 }
423 if (count != 0 && store.data() == nullptr) {
424 return Err(Error::invalid_argument("a nonempty store must not be null"));
425 }
426 reference_database_file result;
427 result.metadata_ = {
428 score_compatibility::current<K, BucketCount, Layout>(), static_cast<uint32_t>(count)
429 };
430 if (!blacklist.keys().empty() && blacklist.kmer_length() != K) {
431 return Err(Error::invalid_argument("store blacklist k mismatch"));
432 }
433 result.blacklist_ = blacklist;
434 result.metadata_.compatibility.blacklist_identity = blacklist.identity();
435 result.metadata_.compatibility.blacklist_version = blacklist.version();
436 result.rows_.resize(count * BucketCount);
437 if (count == 0) return result;
438 CUDDL_TRY(detail::download_store<BucketCount>(store, result.rows_, stream));
439 CUDDL_CUDA_TRY(stream.sync());
440 result.names_.assign(names.begin(), names.end());
441 return result;
442 }();
443 } catch (cuda::cuda_error const& error) {
444 return Err(Error::cuda(static_cast<cudaError_t>(error.status())));
445 } catch (std::bad_alloc const& error) {
446 return Err(Error::resource(error.what()));
447 }
uint32_t kmer_length() const noexcept
std::vector< uint64_t > const & keys() const noexcept
uint64_t identity() const noexcept
uint8_t version() const noexcept
kmer_blacklist const & blacklist() const noexcept
std::span< std::string const > names() const noexcept
Reference labels in reference-ID order.
#define CUDDL_CUDA_TRY(...)
Propagates a CUDA error wrapped in cuddl::Result<void>.
Definition error.hpp:248
auto const count
Definition kernels.cuh:1031
static Error invalid_argument(std::string message)
Definition error.hpp:99
Here is the call graph for this function:

◆ load()

static Result< reference_database_file > cuddl::reference_database_file::load ( std::filesystem::path const &  path)
inlinestatic

Reads and validates a binary database without requiring a GPU.

Definition at line 550 of file reference_database_file.cuh.

552 {
553 return [&]() -> Result<reference_database_file> {
554 detail::database_file_reader reader{
555 std::ifstream(path, std::ios::binary | std::ios::ate)
556 };
557 auto size = reader.input.tellg();
558 if (size < 0) {
559 return Err(Error::resource("cannot open database file: " + path.string()));
560 }
561 reader.remaining = static_cast<uint64_t>(size);
562 reader.input.seekg(0);
563 char magic[8];
564 CUDDL_TRY(reader.bytes(magic, sizeof(magic)));
565 if (std::string_view(magic, sizeof(magic)) != std::string_view("CUDDLDB\0", 8)) {
566 return Err(Error::invalid_argument("not a cuDDL reference database file"));
567 }
568 uint16_t version{};
569 CUDDL_TRY(reader.value(version));
570 if (version != database_file_version) {
571 return Err(Error::invalid_argument("unsupported database file version"));
572 }
573 reference_database_file result;
574 uint32_t blacklist_count = 0;
575 CUDDL_TRY(detail::database_file_metadata(reader, result.metadata_, blacklist_count));
576 auto& c = result.metadata_.compatibility;
577 if (c.kmer_length < 1 || c.kmer_length > 31 || c.bucket_count < 2048 ||
578 c.bucket_count > 8192 || !std::has_single_bit(c.bucket_count) ||
579 (c.key_mask != 0xffffU && c.key_mask != 0x7fffU) || c.exponent_bits == 0 ||
580 c.exponent_bits >= 16) {
581 return Err(Error::invalid_argument("unsupported database construction metadata"));
582 }
583 c.indexed_bucket_count = c.bucket_count;
584 c.mantissa_bits = 16U - c.exponent_bits;
585 c.score_encoder_identity = 1U;
586 c.hash_identity = 1U;
587 c.hash_seed = detail::seed;
588 c.canonicalisation_policy = 1U;
589 c.blacklist_version = blacklist_count == 0 ? 0U : 1U;
590 if (blacklist_count > reader.remaining / sizeof(uint64_t)) {
591 return Err(Error::invalid_argument("invalid blacklist extent"));
592 }
593 std::vector<uint64_t> keys(blacklist_count);
594 CUDDL_TRY(reader.words(keys));
595 if (!std::is_sorted(keys.begin(), keys.end()) ||
596 std::adjacent_find(keys.begin(), keys.end()) != keys.end()) {
597 return Err(Error::invalid_argument("blacklist payload is not sorted and unique"));
598 }
599 result.blacklist_ = kmer_blacklist(c.kmer_length, std::move(keys));
600 if (result.blacklist_.identity() != c.blacklist_identity ||
601 result.blacklist_.version() != c.blacklist_version) {
602 return Err(Error::invalid_argument("blacklist identity mismatch"));
603 }
604 uint64_t const count = result.metadata_.reference_count;
605 uint64_t const words = count * c.bucket_count;
606 uint64_t const payload_bytes = words * sizeof(uint16_t);
607 if (words > std::numeric_limits<size_t>::max() / sizeof(uint16_t) ||
608 payload_bytes + count * sizeof(uint32_t) + sizeof(uint32_t) > reader.remaining) {
609 return Err(Error::invalid_argument("invalid reference database extents"));
610 }
611 result.names_.reserve(count);
612 for (uint64_t id = 0; id < count; ++id) {
613 uint32_t length{};
614 CUDDL_TRY(reader.value(length));
615 auto const required =
616 payload_bytes + (count - id - 1) * sizeof(uint32_t) + sizeof(uint32_t);
617 if (required > reader.remaining || length > reader.remaining - required) {
618 return Err(Error::invalid_argument("invalid reference label extent"));
619 }
620 std::string name(length, '\0');
621 CUDDL_TRY(reader.bytes(name.data(), name.size()));
622 result.names_.push_back(std::move(name));
623 }
624 if (reader.remaining != payload_bytes + sizeof(uint32_t)) {
625 return Err(Error::invalid_argument("unexpected reference database payload size"));
626 }
627 result.rows_.resize(words);
628 CUDDL_TRY(reader.words(result.rows_));
629 auto const expected_checksum = static_cast<uint32_t>(reader.checksum);
630 uint32_t checksum{};
631 CUDDL_TRY(reader.value(checksum));
632 if (checksum != expected_checksum) {
633 return Err(Error::invalid_argument("database checksum mismatch"));
634 }
635 return result;
636 }();
637 } catch (std::invalid_argument const& error) {
638 return Err(Error::invalid_argument(error.what()));
639 } catch (std::bad_alloc const& error) {
640 return Err(Error::resource(error.what()));
641 }
__global__ uint32_t uint32_t uint64_t * keys
constexpr uint64_t seed
Domain separation constant folded into every packed k-mer before hashing.
Definition hash.cuh:18
auto const words
Definition top_k.cuh:204
Result< void > database_file_metadata(IO &io, reference_database_metadata &metadata, uint32_t &blacklist_count)
Here is the call graph for this function:

◆ metadata()

reference_database_metadata cuddl::reference_database_file::metadata ( ) const
inlinenoexcept

Compatibility metadata and reference count.

Definition at line 277 of file reference_database_file.cuh.

277 {
278 return metadata_;
279 }
Here is the caller graph for this function:

◆ names()

std::span< std::string const > cuddl::reference_database_file::names ( ) const
inlinenoexcept

Reference labels in reference-ID order.

Definition at line 281 of file reference_database_file.cuh.

281 {
282 return names_;
283 }
Here is the caller graph for this function:

◆ rows()

std::span< uint16_t const > cuddl::reference_database_file::rows ( ) const
inlinenoexcept

Winner scores in reference-ID order, one bucket row per genome.

Definition at line 285 of file reference_database_file.cuh.

285 {
286 return rows_;
287 }
Here is the caller graph for this function:

◆ save()

Result< void > cuddl::reference_database_file::save ( std::filesystem::path const &  path) const
inline

Writes a versioned, little-endian, CRC32-checked binary file.

Writes to an exclusive temporary file in the destination directory, closes it, then atomically replaces the destination. Failure leaves an existing destination intact. This guarantees atomic publication, not durability across power loss.

Definition at line 474 of file reference_database_file.cuh.

474 {
475 return [&]() -> Result<void> {
476 detail::database_file_writer writer;
477 writer.temporary = path.string() + ".tmp.XXXXXX";
478 auto fd = ::mkstemp(writer.temporary.data());
479 if (fd < 0) {
480 writer.temporary.clear();
481 return Err(Error::resource("cannot create database file beside: " + path.string()));
482 }
483 writer.output = ::fdopen(fd, "wb");
484 if (!writer.output) {
485 ::close(fd);
486 return Err(Error::resource("cannot open temporary database file"));
487 }
488 CUDDL_TRY(writer.bytes("CUDDLDB\0", 8));
489 CUDDL_TRY(writer.value(database_file_version));
490 auto metadata = metadata_;
493 return Err(
494 Error::invalid_argument("database is missing its construction blacklist")
495 );
496 }
497 auto blacklist_count = static_cast<uint32_t>(blacklist_.keys().size());
498 CUDDL_TRY(detail::database_file_metadata(writer, metadata, blacklist_count));
499 CUDDL_TRY(writer.words(blacklist_.keys()));
500 auto const prefix = ::ftello(writer.output);
501 auto const limit = static_cast<uint64_t>(std::numeric_limits<off_t>::max());
502 if (prefix < 0 || static_cast<uint64_t>(prefix) > limit - sizeof(uint32_t)) {
503 return Err(Error::resource("cannot size temporary database file"));
504 }
505 uint64_t bytes = static_cast<uint64_t>(prefix) + sizeof(uint32_t);
506 if (rows_.size() > (limit - bytes) / sizeof(uint16_t)) {
507 return Err(Error::resource("database exceeds file size capacity"));
508 }
509 bytes += rows_.size() * sizeof(uint16_t);
510 for (auto const& name : names_) {
511 if (name.size() > std::numeric_limits<uint32_t>::max()) {
512 return Err(Error::resource("reference label exceeds binary format capacity"));
513 }
514 if (bytes > limit - sizeof(uint32_t) ||
515 name.size() > limit - bytes - sizeof(uint32_t)) {
516 return Err(Error::resource("database exceeds file size capacity"));
517 }
518 bytes += sizeof(uint32_t) + name.size();
519 }
520 // Allocate before names spill from stdio, avoiding delayed extent allocation when
521 // the completed temporary file atomically replaces an existing database.
522 auto const allocation = ::posix_fallocate(fd, 0, static_cast<off_t>(bytes));
523 if (allocation != 0 && allocation != EOPNOTSUPP && allocation != ENOSYS) {
524 return Err(
526 "cannot allocate database file: " +
527 std::generic_category().message(allocation)
528 )
529 );
530 }
531 for (auto const& name : names_) {
532 CUDDL_TRY(writer.value(static_cast<uint32_t>(name.size())));
533 CUDDL_TRY(writer.bytes(name.data(), name.size()));
534 }
535 CUDDL_TRY(writer.words(rows_));
536 CUDDL_TRY(writer.value(static_cast<uint32_t>(writer.checksum)));
537 auto closed = std::fclose(std::exchange(writer.output, nullptr));
538 if (closed != 0) return Err(Error::resource("cannot close database file"));
539 std::filesystem::rename(writer.temporary, path);
540 writer.temporary.clear();
541 return Ok();
542 }();
543 } catch (std::filesystem::filesystem_error const& error) {
544 return Err(Error::resource(error.what()));
545 } catch (std::bad_alloc const& error) {
546 return Err(Error::resource(error.what()));
547 }
reference_database_metadata metadata() const noexcept
Compatibility metadata and reference count.
Result< void > Ok() noexcept
Success return for Result<void>; same as return {}.
Definition error.hpp:181
Here is the call graph for this function:

◆ upload()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< reference_database< K, BucketCount, Layout > > cuddl::reference_database_file::upload ( cuda::stream_ref  stream) const
inline

Uploads reference score rows and labels without constructing an index.

Definition at line 451 of file reference_database_file.cuh.

453 {
454 CUDDL_TRY((detail::validate_indexed_score_compatibility<K, BucketCount, Layout>(
455 metadata_.compatibility
456 )));
457 auto rows =
458 CUDDL_CUDA_TRY(cuda::make_device_buffer<uint16_t>(stream, stream.device(), rows_));
460 {rows.data(), rows.size()}, metadata_.compatibility, stream
461 )));
462 database.names_ = names_;
463 CUDDL_CUDA_TRY(stream.sync());
464 return std::move(database);
465 }
std::span< uint16_t const > rows() const noexcept
Winner scores in reference-ID order, one bucket row per genome.
static Result< reference_database > build_async(device_span< score_type const > rows, score_compatibility compatibility, cuda::stream_ref stream)
Builds a database from flat row-major scores on stream.
Here is the call graph for this function:

Friends And Related Symbol Documentation

◆ detail::reference_file_assembly

friend struct detail::reference_file_assembly
friend

Definition at line 645 of file reference_database_file.cuh.

◆ reference_index_file

friend class reference_index_file
friend

Definition at line 644 of file reference_database_file.cuh.


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