cuDDL
Loading...
Searching...
No Matches
Namespaces | Classes | Typedefs | Enumerations | Functions | Variables
cuddl::detail Namespace Reference

Namespaces

namespace  compact_impl
 
namespace  device_gzip
 

Classes

struct  bases_packer
 A base packer and the bases it takes per step, a multiple of sixteen up to 64. More...
 
struct  blacklist_view
 Exact membership within the candidate's destination bucket. More...
 
struct  cardinality_payload
 Per-thread accumulator feeding the CUB block reduction for cardinality. More...
 
struct  cardinality_scratch
 Device-visible scratch for the owning cardinality wrapper. More...
 
struct  database_file_reader
 
struct  database_file_writer
 
class  database_stager
 Encodes genome records into register rows through a device arena. More...
 
struct  decoded_fasta
 
struct  decompression_source
 Supplies storage for one file, or a null target when none is available. More...
 
struct  decompression_target
 Host-visible buffer a caller offers for decompressed bytes. More...
 
class  device_fasta_pipeline
 Normalises FASTA batches supplied compressed, uncompressed, or already inflated. More...
 
struct  fasta_parse_result
 Result of parsing one FASTA/FASTQ file into packed canonical k-mers. More...
 
class  fastx_fastq_reader
 FASTQ parser over a contiguous in-memory buffer. More...
 
class  fastx_load_pool
 
class  fastx_mapped_file
 Read-only contiguous file payload for in-memory FASTX parsing. More...
 
struct  fastx_packed_record
 One run of bases packed two bits each: its first word and its length in bases. More...
 
struct  fastx_sequence_extent
 A contiguous run of sequence bases (record headers and line breaks excluded). More...
 
struct  fastx_sequence_file
 
struct  fastx_sequence_span
 One parallel scan span of the combined FASTA sequence stream. More...
 
class  file_writer
 Writes a file from a thread of its own, so the caller produces the next bytes while earlier ones reach storage. More...
 
struct  gzip_file_probe
 What a gzip file's header and trailer say, or that a file is plain FASTA, read without inflating it. More...
 
struct  inflated_genome
 One genome the device inflated: its compacted bases, resident on the device. More...
 
class  io_ring
 Stand-in where the build has no io_uring headers: never available. More...
 
struct  packed_identity
 Weighted k-mer identity of one packed result, for the GPU conversion. More...
 
class  path_loaders
 Loader state a build fed by paths keeps for its whole run. More...
 
class  pinned_pool_trim
 Returns the free pages of a device_pinned_pool to the OS when it leaves scope. More...
 
struct  plane_counts
 Bit-sliced pairwise counts of one 32-bucket group, accumulated per lane. More...
 
struct  posting_bitmap_cell
 Selects posting lists whose bitmap uses no more storage than their reference IDs. More...
 
class  reference_database_view
 Non-owning, trivially copyable view of one selected reference-row backing. More...
 
struct  reference_delta_view
 Reference rows stored as a base reference plus the buckets that differ from it. More...
 
struct  reference_deltas
 
struct  reference_file_assembly
 Builds a reference_database_file from gathered parts; the file's members are private. More...
 
struct  reference_index_digest
 
struct  refine_cells
 How a refinement-style block splits a tile: cell c holds query group c % groups and the reference words from first_word + (c / groups) * block_words. More...
 
struct  sequence_batch_chunk
 One piece of sequence of one genome, and where its bytes are. More...
 
class  sequence_buffer_pool
 
struct  sequence_genome
 One genome as its records, in order. No records keeps the genome's ID with a zero row. More...
 
struct  sequence_record
 One genome of bases the caller already holds. More...
 
struct  shared_gzip_decompressor
 libdeflate handle, one per thread because the object is single-threaded. More...
 
struct  sketch_scratch
 Persistent zero-copy scratch used by owning synchronous result wrappers. More...
 
class  sketch_view
 Non-owning implementation view of a DDL sketch. More...
 
struct  span_result
 One worker's rolling-window state and output. More...
 
struct  sparse_index_key
 
struct  sparse_reference_id
 
struct  sparse_segment_offset
 
struct  staging_plan
 Sizing a build resolves before its first genome. More...
 
struct  summary_payload
 Per-thread accumulator feeding a CUB block reduction for fused summaries. More...
 
struct  top_k_row_offset
 Start of genome row i of a collector's heaps, rows of k slots. More...
 
struct  top_k_slots
 Each genome's k best neighbours so far: keys and packed counts in parallel rows of k, each row a min-heap of keys whose root is the row's worst key, zero while the row has room. More...
 
struct  word_popcount
 Popcount of one pass-bit word. More...
 
class  worker_pool
 Fixed threads that run indexed work with the caller, for work repeated per batch. More...
 

Typedefs

using fastx_line_end_fn = decltype(&fastx_line_end_scalar)
 
using device_scope = ::cuda::__ensure_current_context
 Makes stream's device current for the enclosing scope and restores the previous context on exit.
 

Enumerations

enum class  decompression_backend : uint8_t {
  automatic , hybrid , cpu , gpu ,
  gpu_cores
}
 Where inputs are decompressed; sketch construction always runs on the GPU. More...
 
enum class  transfer_mode : uint8_t { automatic , in_place , pinned , staged }
 How a build gets CPU-parsed sequence bytes to the device. More...
 
enum class  fastx_format : uint8_t { unknown , fasta , fastq }
 Detected record format for a FASTX buffer. More...
 
enum class  hybrid_variant : uint8_t { bbtools , paper }
 Which HybridDDL blend the variant kernel computes. More...
 
enum class  refine_candidates { bitmap , all , reference_mask }
 Pairs refine_batch_bitmap_kernel compares. More...
 
enum class  pinned_placement : uint8_t { device_node , host }
 Where a device_pinned_pool takes its pages. More...
 

Functions

constexpr bool ablated (std::string_view) noexcept
 Always false: this build compiles the ablation switches out.
 
__host__ __device__ double minimum_mle (double bucket_count, double empty_count, double sum_restored) noexcept
 Estimates the number of distinct hashes from the minimum hash in each bucket.
 
__host__ __device__ double mean_m (double bucket_count, double filled, double sum_restored) noexcept
 MeanM estimate retained as the full-sketch form of minimum_mle.
 
__host__ __device__ double cardinality (double bucket_count, double empty_count, double sum_restored) noexcept
 Public DDL cardinality estimate.
 
__host__ __device__ float minimum_mle_f32 (float bucket_count, float empty_count, float sum_restored) noexcept
 Single-precision variants of the cardinality estimators.
 
__host__ __device__ float mean_m_f32 (float bucket_count, float filled, float sum_restored) noexcept
 Single-precision mean_m for the throughput-bound cardinality reduction.
 
__host__ __device__ float cardinality_f32 (float bucket_count, float empty_count, float sum_restored) noexcept
 Single-precision cardinality for the throughput-bound cardinality reduction.
 
__device__ void classify (pairwise_counts &target, uint32_t this_score, uint32_t other_score) noexcept
 Classifies one score relative to this into target.
 
template<size_t BucketCount, typename Layout = default_register_layout>
__host__ Result< void > launch_construction (device_span< uint64_t const > input, device_span< uint32_t > registers, cuda::stream_ref stream, cuda::std::optional< blacklist_view > blacklist=cuda::std::nullopt)
 Launches construction of a sketch through a CTA-local shared register array merged into registers (add_shared_kernel).
 
bool device_reads_pageable_memory (cuda::device_ref device) noexcept
 Whether device reads pageable host memory itself.
 
bool stages_in_place (transfer_mode mode, cuda::device_ref device) noexcept
 Whether a build in mode stages the caller's bytes in place.
 
bool pages_locked (transfer_mode mode, cuda::device_ref device) noexcept
 Whether a build in mode decompresses into page-locked buffers.
 
Result< size_t > available_device_bytes (cuda::stream_ref stream)
 Device bytes an allocation on stream can still get.
 
template<uint32_t K, size_t BucketCount>
Result< staging_plan > plan_staging (size_t references, uint64_t staged_ceiling, std::optional< size_t > staging_bytes, cuda::stream_ref stream)
 Sizes the arena and the row store from free device memory and the corpus.
 
template<size_t BucketCount>
Result< void > download_store (device_span< uint32_t const > store, std::span< uint16_t > scores, cuda::stream_ref stream)
 Copies the winner scores of device store rows into host score rows.
 
decompression_target acquire_sequence_target (void *context, size_t bytes)
 decompression_source trampoline for sequence_buffer_pool.
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout, typename Fill , typename Sink >
Result< void > stage_groups (size_t genomes, uint64_t staged_ceiling, std::optional< size_t > staging_bytes, cuda::stream_ref stream, reference_build_statistics *statistics, Fill &&fill, Sink &&sink, bool in_place=false, cuda::std::optional< blacklist_view > blacklist=cuda::std::nullopt)
 Runs the staging loop over a collection, filling one group at a time.
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout, typename Sink >
Result< void > stage_paths (std::span< std::filesystem::path const > paths, cuda::stream_ref stream, std::optional< size_t > staging_bytes, unsigned parser_workers, transfer_mode transfer, decompression_backend decompression, bool direct_reads, reference_build_statistics *statistics, Sink &&sink, cuda::std::optional< blacklist_view > blacklist=cuda::std::nullopt)
 Stages a path collection, handing each group's device rows to sink.
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout, typename Sink >
Result< void > stage_sequences (std::span< sequence_genome const > genomes, cuda::stream_ref stream, std::optional< size_t > staging_bytes, reference_build_statistics *statistics, Sink &&sink, cuda::std::optional< blacklist_view > blacklist=cuda::std::nullopt)
 Stages bases the caller already holds, handing each group's device rows to sink.
 
uint32_t bgzf_block_size (unsigned char const *header) noexcept
 A BGZF block's total size from its 18-byte header, or 0 if it is not one: a gzip header with only FEXTRA set, holding just the BC subfield.
 
uint32_t crc32_combine (uint32_t a, uint32_t b, uint64_t length) noexcept
 CRC32 of A followed by B, from CRC32(A), CRC32(B) and B's length, as zlib's crc32_combine: CRC32(A) times x^(8 * length) modulo the reflected polynomial, plus CRC32(B).
 
template<typename Block >
std::optional< std::pair< uint32_t, uint64_t > > walk_bgzf (unsigned char const *bytes, uint64_t size, Block &&block)
 Calls block(deflate_at, deflate_bytes, inflated) for each non-empty block of the BGZF file in bytes.
 
gzip_file_probe probe_gzip_file (std::filesystem::path const &path)
 Reads the header and trailer of path, and of every block of a BGZF file.
 
template<typename Work >
void parallel_for (size_t count, size_t workers, Work &&work)
 Runs work(index) for every index below count on workers threads.
 
bool has_gzip_signature_portable (char const *data, size_t size)
 True when data holds what could start another gzip member: 1f 8b 08, then a flag byte with its reserved bits clear.
 
auto resolve_gzip_signature_scanner () -> bool(*)(char const *, size_t)
 Selects the widest signature scanner the host supports.
 
constexpr uint8_t encode_base (char base) noexcept
 Maps one DNA byte to its 2-bit symbol (A=0, C=1, T=2, G=3) or 0xff when invalid.
 
constexpr uint64_t reverse_complement (uint64_t packed, uint32_t k) noexcept
 Reverse-complements a packed k-mer (2 bits per base) with bit-parallel reversal.
 
std::vector< fastx_sequence_span > fastx_split_sequence_spans (std::vector< fastx_sequence_extent > const &extents, uint32_t seed_bases, uint32_t count)
 Splits the combined FASTA sequence stream into count contiguous spans.
 
void consume_byte (char ch, uint32_t k, uint64_t mask, span_result &result, uint64_t &window, uint32_t &window_len)
 Feeds one byte of sequence through the DDL rolling-window extraction.
 
Result< fasta_parse_result > parse_fasta (std::string const &path, uint32_t k, unsigned threads=std::thread::hardware_concurrency())
 Parses a FASTA/FASTQ file's sequence bases into packed canonical k-mers of length k.
 
constexpr bool fastx_is_sequence_whitespace (char ch) noexcept
 True for bytes that FASTA sequence consumers conventionally skip between bases.
 
size_t fastx_line_end_scalar (std::string_view data, size_t position)
 
fastx_line_end_fn resolve_fastx_line_end ()
 Selects the fastest line-end scanner the host supports.
 
size_t fastx_line_end (std::string_view data, size_t position)
 First line-end position at or after position using the resolved scanner.
 
uint32_t pack_bases16_portable (char const *bases, size_t count, uint32_t &word)
 Packs up to sixteen bases into a two-bit word, base j in bits 2j and 2j+1.
 
uint64_t pack_bases_pieces (char const *bases, size_t count, uint32_t *words, size_t width)
 Packs width bases from sixteen-base pieces, for a tail shorter than a full step.
 
uint64_t pack_bases_portable (char const *bases, size_t count, uint32_t *words)
 
bases_packer resolve_bases_packer ()
 Selects the widest base packer the host supports.
 
void pack_fastx_sequence_file (fastx_sequence_file &result, uint32_t k)
 Packs a parsed file's records in place into two-bit words, run by run.
 
libdeflate_result decompress_gzip_member (libdeflate_decompressor *decoder, std::string_view input, char *output, size_t capacity, size_t &consumed, size_t &produced)
 Decodes one gzip member, checking structure and ISIZE but not checksums.
 
Result< void > gunzip_members_into (std::string_view input, std::string &output, std::string const &path)
 Decompresses gzip members from a mapped buffer into output, appending.
 
Result< size_t > gunzip_members_direct (std::string_view input, decompression_target target, bool &insufficient, std::string const &path)
 Decompresses gzip members straight into a caller's buffer.
 
uint64_t gzip_decompressed_size (std::string const &path)
 Uncompressed size the gzip trailer advertises, or 0 when it cannot be read.
 
size_t gzip_size_hint (std::string_view input)
 
char * compact_sequence_whitespace (char const *first, char const *last, char *out)
 Copies bases from [first, last) to out, dropping sequence whitespace.
 
char const * fasta_next_header (char const *first, char const *last)
 First '>' in [first, last) that starts a line, or last.
 
char * fasta_sequence_compact (char const *first, char const *last, char *out, char const *&stop)
 Compacts one FASTA record's sequence from first to out, stopping at the next header.
 
std::vector< fastx_sequence_extent > fastx_fasta_compact_extents (std::string_view data, char *out)
 Collects FASTA sequence extents with whitespace compacted out, in one pass over data.
 
void compact_fastx_sequence_extents (fastx_sequence_file &result)
 Compacts sequence whitespace in place, leaving every extent a run of bases.
 
Result< void > parse_fastx_sequence_file (fastx_sequence_file &result, std::string const &path)
 
Result< void > pack_fasta_chunks (fastx_sequence_file &result, std::string_view data, char *out, uint32_t k, std::string const &path)
 Compact and pack one cache-sized block at a time, without a genome-sized ASCII write.
 
Result< std::unique_ptr< fastx_sequence_file > > finish_fastx_sequence_file (std::unique_ptr< fastx_sequence_file > result, std::string_view data, std::string const &path, bool defer_fasta, uint32_t pack_k)
 Parses, packs or defers the bytes data of a loaded file, which result holds.
 
Result< std::optional< std::string_view > > inflate_and_pack (fastx_sequence_file &result, std::string_view input, std::string const &path, decompression_source source, uint32_t k)
 Inflates gzip input in this thread's reusable heap buffer and, for FASTA, packs it there and moves only the packed words into a buffer from source.
 
Result< std::unique_ptr< fastx_sequence_file > > decode_fastx_sequence_file (std::unique_ptr< fastx_mapped_file > file, std::string const &path, decompression_source source, bool defer_fasta, uint32_t pack_k=0)
 
Result< std::unique_ptr< fastx_sequence_file > > load_fastx_sequence_file (std::string const &path, decompression_source source={}, bool defer_fasta=false, bool read=false, uint32_t pack_k=0)
 
__host__ __device__ constexpr uint64_t splitmix64 (uint64_t value) noexcept
 SplitMix64 finalizer, applied to a packed k-mer XOR the domain seed.
 
__host__ __device__ constexpr uint64_t hash_kmer (uint64_t packed_kmer) noexcept
 Hashes a canonical packed k-mer into the value space used by DDL registers.
 
template<size_t BucketCount>
__host__ __device__ constexpr size_t bucket_of (uint64_t hash) noexcept
 Selects the register bucket for hash given a power-of-two BucketCount.
 
constexpr __host__ __device__ __forceinline__ uint64_t rotl64 (uint64_t x, int8_t r)
 Rotates x left by r bits.
 
template<typename T >
__host__ __device__ __forceinline__ T load_chunk (const uint8_t *data, uint64_t index)
 Loads a chunk of type T from data at byte offset index*sizeof(T).
 
constexpr __host__ __device__ __forceinline__ uint64_t finalize (uint64_t h)
 Applies the xxHash-64 final mixing (avalanche) step.
 
__host__ __device__ uint64_t xxhash64 (const uint8_t *bytes, uint64_t size, uint64_t seed)
 Computes xxHash-64 over a byte range without allocating or copying it.
 
template<typename T >
__host__ __device__ uint64_t xxhash64 (const T &key, uint64_t seed=0)
 Computes xxHash-64 over the raw bytes of a value.
 
__device__ double interpolate_mean_m_cf (double estimate) noexcept
 Interpolates the MeanM correction factor for estimate from the calibration table.
 
__device__ double lc_min (uint32_t const *bins, double bucket_count) noexcept
 Linear-counting estimate from the first occupied NLZ tier.
 
__device__ double dlc (uint32_t const *bins, double bucket_count, double lc) noexcept
 Distributed linear-counting estimate, blended from per-tier estimates by precision.
 
__device__ double hybrid (double low_estimate, double zone_estimate, double corrected_mean, double bucket_count) noexcept
 Log-blended low and corrected estimates across the hybrid transition zone.
 
__device__ double bbtools_mean_m (double bucket_count, double filled, double restored_sum) noexcept
 BBTools occupancy-corrected MeanM estimate from restored magnitudes.
 
__device__ hybrid_cardinality_estimates hybrid_estimates (uint32_t const *bins, double bucket_count, double restored_sum) noexcept
 Fused BBTools, paper, linear-counting, DLC, and raw MeanM estimates.
 
__device__ float interpolate_mean_m_cf_f32 (float estimate) noexcept
 Single-precision variant of the hybrid estimator tail.
 
__device__ float lc_min_f32 (uint32_t const *bins, float bucket_count) noexcept
 Single-precision lc_min for the latency-bound hybrid scan.
 
__device__ float dlc_f32 (uint32_t const *bins, float bucket_count, float lc) noexcept
 Single-precision dlc for the latency-bound hybrid scan.
 
__device__ float hybrid_f32 (float low_estimate, float zone_estimate, float corrected_mean, float bucket_count) noexcept
 Single-precision hybrid for the latency-bound hybrid scan.
 
__device__ float bbtools_mean_m_f32 (float bucket_count, float filled, float restored_sum) noexcept
 Single-precision bbtools_mean_m for the latency-bound hybrid scan.
 
__device__ hybrid_cardinality_estimates hybrid_estimates_f32 (uint32_t const *bins, float bucket_count, float restored_sum) noexcept
 Single-precision hybrid_estimates for the latency-bound hybrid scan.
 
template<size_t BucketCount, typename Layout = default_register_layout, bool HasBlacklist = false>
__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.
 
__device__ uint2 lane_posting_range (uint32_t const *offsets, uint16_t const *sorted_keys, uint32_t reference_count, uint32_t bucket, uint32_t key, uint32_t key_count)
 One thread's posting range for a bucket/key cell of a dense or sparse index.
 
__host__ __device__ constexpr uint32_t sparse_directory_shift (uint32_t reference_count) noexcept
 Low key bits a sparse key directory cell spans for reference_count references.
 
__host__ __device__ constexpr uint32_t sparse_directory_entries (uint32_t reference_count) noexcept
 Bounds per bucket in a sparse key directory.
 
__global__ __launch_bounds__ (block_size) static void build_sparse_directory_kernel(uint16_t const *sorted_keys
 Builds a sparse key directory: entry c of bucket b is the first position in b's sorted keys whose key is at least c << shift.
 
 for (auto i=static_cast< size_t >(blockIdx.x) *block_size+threadIdx.x;i< total;i+=static_cast< size_t >(gridDim.x) *block_size)
 
 if (local !=0U)
 
__device__ uint2 directory_posting_range (uint16_t const *sorted_keys, uint32_t const *directory, uint32_t reference_count, uint32_t bucket, uint32_t sparse_key)
 One sparse bucket/key posting range, narrowed through the bucket's key directory.
 
template<size_t BucketCount, typename QueryRow >
__global__ __launch_bounds__ (block_size) void count_batch_index_matches_kernel(QueryRow const *queries
 Counts index matches for every query/reference pair in one tile.
 
bool uses_tiled_index_counts (uint32_t reference_count, uint32_t query_count, uint32_t indexed_bucket_count) noexcept
 Whether a batch counts through count_batch_index_tile_kernel.
 
uint32_t index_tile_references (uint32_t reference_count, uint32_t query_count, uint32_t multiprocessors, uint32_t max_shared_bytes) noexcept
 References per tile, bounded by the device's shared memory and small enough that the batch launches about four blocks per SM.
 
template<size_t BucketCount>
__global__ void hybrid_posting_ranges_kernel (uint16_t const *queries, uint32_t const *offsets, uint32_t const *bitmap_cells, uint32_t bitmap_count, uint32_t words, uint32_t indexed_buckets, uint16_t key_mask, uint2 *ranges, uint32_t *bitmap_starts, uint32_t *bitmap_counts)
 Splits each query's posting ranges into short lists and bitmap offsets.
 
template<uint32_t CounterBits>
__device__ uint32_t sum_posting_bitmaps (uint32_t const *bitmaps, uint32_t const *starts, uint32_t count, uint32_t word, uint32_t minimum_matches, uint32_t passed, uint32_t(&counters)[CounterBits])
 Sums one query's posting bitmaps over one word of references in bit-sliced counters.
 
template<size_t BucketCount, uint32_t CounterBits, typename QueryRow >
__global__ __maxnreg__ (48) void count_batch_index_tile_kernel(QueryRow const *queries
 Counts index matches for one query against one reference tile in shared memory.
 
 if (candidate_bits !=nullptr &&all_to_all &&high<=query_id_offset+query_index+1U)
 
if constexpr (CounterBits !=0U)
 
 __syncthreads ()
 
 if (candidate_bits==nullptr)
 
template<typename ReferenceScore >
__device__ __forceinline__ void classify_query_words (pairwise_counts &target, uint32_t const (&q)[8], ReferenceScore const *reference) noexcept
 Classifies 16 buckets of an already loaded 16-bit query chunk against one load_256_alignment aligned reference chunk.
 
template<typename SearchResult >
__device__ void refine_write_result (uint32_t index, uint32_t query_index, uint32_t reference_id, uint32_t query_id_offset, uint32_t const *match_counts, uint32_t pair_id, pairwise_counts local, uint32_t lane, SearchResult *results, uint32_t *result_match_counts)
 Warp-reduces one refined candidate and writes its stable result slot.
 
__host__ __device__ constexpr uint32_t score_plane_index (uint32_t group, uint32_t plane) noexcept
 Position of plane plane of 32-bucket group group within a row's bit-planes.
 
__device__ __forceinline__ void write_score_planes (uint32_t score, uint32_t group, uint32_t lane, uint32_t *planes)
 Writes the bit-planes of one 32-bucket group whose scores the warp holds, one per lane.
 
template<size_t BucketCount>
__global__ __launch_bounds__ (block_size) void build_score_planes_kernel(uint16_t const *rows
 Transposes compact score rows into the bit-plane layout of score_plane_index.
 
__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).
 
__device__ __forceinline__ uint32_t plane_any (uint32_t const (&planes)[score_planes]) noexcept
 Buckets of a plane group holding a nonzero score.
 
__device__ __forceinline__ uint32_t plane_low_any (uint32_t const (&planes)[score_planes]) noexcept
 Buckets of a plane group whose score is nonzero below the top plane.
 
template<bool CountMatches, bool ReferenceFull = false>
__device__ __forceinline__ void compare_plane_group (uint32_t const (&q)[score_planes], uint32_t const (&r)[score_planes], uint32_t reference_any, uint32_t reference_low_any, uint16_t key_mask, uint32_t match_lanes, plane_counts &counts) noexcept
 Adds one group's comparison of query planes q with reference planes r, whose nonzero buckets reference_any the caller hoists out of per-query loops.
 
uint32_t warp_grid_blocks (size_t warps) noexcept
 Blocks for a grid-stride launch covering warps warps of block_size threads.
 
__device__ __forceinline__ uint32_t next_cell (uint32_t *cell_counter, uint32_t cell, uint32_t &claimed)
 The cell a refinement-style block processes after cell: claimed from cell_counter, a zeroed per-launch counter, so blocks balance uneven cells; without one, grid-stride.
 
template<bool UpperTriangle>
__device__ __forceinline__ refine_cells make_refine_cells (uint32_t query_id_offset, uint32_t query_count, uint32_t group, uint32_t words_per_query, uint32_t max_block_words, uint32_t waves)
 Splits a tile into refine_cells, about waves cells per block when the tile allows, so that blocks claiming cells dynamically even out cells of uneven work.
 
template<bool UpperTriangle>
__host__ __device__ uint64_t batch_result_slot (uint32_t query_index, uint32_t reference_id, uint32_t query_id_offset, uint32_t reference_count) noexcept
 Position of the pair (query_index of the tile, reference_id) in a tile's results: rows of reference_count references per query, or for all-to-all tiles the packed strict upper triangle of pairs with query_id < reference_id, where query IDs start at query_id_offset.
 
template<size_t BucketCount, bool CountMatches, typename SearchResult , refine_candidates Candidates, bool UpperTriangle, uint32_t QueryGroup>
__global__ __launch_bounds__ (bitmap_refine_block_size) void refine_batch_bitmap_kernel(uint32_t const *query_planes
 Exactly compares query bit-planes with references, sharing each reference row across a query group.
 
__device__ __forceinline__ void load_plane_chunk (uint4 const *row, uint32_t group, uint32_t chunk, uint32_t(&words)[4])
 Loads plane chunk chunk (planes 4 chunk ... 4 chunk + 3) of group group of a bit-plane row (score_plane_index layout).
 
template<size_t BucketCount, bool UpperTriangle, uint32_t QueryGroup>
__global__ void match_filter_kernel (uint32_t const *query_planes, uint32_t query_id_offset, uint32_t query_count, uint32_t const *reference_planes, uint32_t reference_count, uint32_t indexed_bucket_count, uint32_t minimum_matches, uint32_t *candidate_bits, uint32_t *cell_counter)
 Records in candidate_bits (candidate-bitmap layout) the pairs of the layout whose scores agree in their low match_filter_planes planes in at least minimum_matches buckets.
 
template<size_t BucketCount, typename SearchResult , bool UpperTriangle, uint32_t QueryGroup>
__global__ void refine_all_pairs_kernel (uint32_t const *query_planes, uint32_t query_id_offset, uint32_t query_count, uint32_t const *reference_planes, uint32_t reference_count, SearchResult *results, uint32_t *cell_counter, uint32_t const *reference_mask)
 Exactly compares the layout pairs selected by reference_mask, without match counts.
 
template<size_t BucketCount, typename SearchResult , bool UpperTriangle, bool Swar>
__global__ __launch_bounds__ (block_size) void row_major_all_pairs_kernel(uint16_t const *rows
 The all-pairs comparison of refine_all_pairs_kernel over row-major 16-bit scores instead of bit-planes, one warp per pair, for ablation builds to compare with.
 
template<size_t BucketCount, typename Layout = default_register_layout>
__global__ void cardinality_kernel (uint32_t const *registers, uint64_t *empty_out, double *estimate_out)
 Computes the cardinality of a single constructed sketch.
 
template<size_t BucketCount, typename Layout = default_register_layout>
__global__ void hybrid_cardinality_kernel (uint32_t const *const registers, hybrid_cardinality_estimates *const estimates)
 Computes BBTools and paper-style HybridDDL estimates in one register scan.
 
template<size_t BucketCount, hybrid_variant Variant, typename Layout = default_register_layout>
__global__ void hybrid_cardinality_variant_kernel (uint32_t const *const registers, double *const estimate)
 Computes one HybridDDL variant estimate for estimator comparisons.
 
template<size_t BucketCount, typename Layout = default_register_layout>
__global__ void batch_cardinality_kernel (uint32_t const *const registers, uint32_t const row_count, uint64_t *const empty_out, double *const estimates_out)
 Computes the cardinality of every stored sketch in one launch.
 
template<size_t BucketCount>
__global__ void batch_scores_kernel (uint32_t const *const registers, uint32_t const row_count, uint16_t *const scores_out)
 Copies the winning score of every register into compact row-major scores.
 
cuda::pinned_memory_pool_ref device_pinned_pool (cuda::device_ref device, pinned_placement placement)
 A page-locked memory pool accessible from device only.
 
template<typename Layout = default_register_layout>
__host__ __device__ uint16_t score (uint64_t hash) noexcept
 Computes the 16-bit DDL rarity score for hash.
 
template<typename Layout = default_register_layout>
__host__ __device__ constexpr uint64_t restore (uint16_t stored) noexcept
 Reconstructs the approximate original hash magnitude from a stored score.
 
template<typename Layout = default_register_layout>
__host__ __device__ constexpr uint64_t restore_midpoint (uint16_t stored) noexcept
 Reconstructs the interval midpoint for restore.
 
__device__ __forceinline__ uint32_t load_shared_relaxed (uint32_t const *address)
 Relaxed CTA-scope load of a shared word that other threads update atomically.
 
template<size_t BucketCount, typename Layout , bool HasBlacklist = false, bool Naive = false>
__device__ __forceinline__ void add_sequence_windows (char const *sequence, uint32_t windows, size_t first_tile, size_t tile_stride, uint32_t k, uint32_t *registers, bool packed_input=false, blacklist_view blacklist={})
 Encodes raw ASCII windows into registers using shared overlapping tiles.
 
template<size_t BucketCount, typename Layout , bool HasBlacklist = false, bool Naive = false>
__global__ void add_sequence_batch_kernel (sequence_batch_chunk const *chunks, size_t chunk_count, size_t block_count, uint32_t k, uint32_t *registers, blacklist_view blacklist={})
 Batch kernel covering every staged chunk in one launch, including short records.
 
template<size_t BucketCount, typename Layout , bool HasBlacklist = false>
__global__ void add_sequence_single_kernel (char const *sequence, uint32_t windows, uint32_t k, uint32_t *registers, blacklist_view blacklist={})
 Single-sequence kernel for the public raw-ASCII API.
 
template<size_t BucketCount, typename Layout = default_register_layout>
__host__ Result< void > launch_sequence_add (device_span< char const > sequence, uint32_t k, device_span< uint32_t > registers, cuda::stream_ref stream, cuda::std::optional< blacklist_view > blacklist=cuda::std::nullopt)
 Shared launch for one device-resident ASCII chunk.
 
Result< void > mpi_try (int status, char const *call)
 
Result< void > agree (Result< void > local, MPI_Comm comm)
 Returns local on the rank that failed, and on every other rank an error naming the lowest failing rank, so that all ranks leave a collective step together.
 
Result< std::vector< std::filesystem::path > > share_listing (std::filesystem::path const &input, int rank, MPI_Comm comm)
 Rank 0's listing of input on every rank.
 
size_t slice_begin (std::span< uint64_t const > bytes, uint64_t total, int rank, int size)
 First genome of rank rank's slice: contiguous slices of about equal bytes.
 
Result< std::vector< uint32_t > > slice_counts (reference_database_file const &mine, genome_slice const &slice, MPI_Comm comm)
 Every rank's genome count, after checking collectively that mine holds the sketches of this rank's slice and that every rank built with rank 0's settings.
 
Result< MPI_Datatype > row_type (uint32_t buckets)
 One row of buckets scores as a single MPI element, so counts of rows stay within int.
 
template<typename Search >
Result< void > shared_search (MPI_Comm comm, Search &&search)
 Runs search with a replica_search that shares tiles across the ranks of comm.
 
template<typename T >
T try_unwrap_success (Result< T > &result)
 
void try_unwrap_success (Result< void > &result)
 
cuda::std::unexpected< Error > propagate_error (const Error &error)
 Copies error for propagation (avoids moving out of expected::error()).
 
__host__ __device__ constexpr uint32_t load_256_alignment () noexcept
 Byte alignment load_256_global_nc requires on the compiled architecture.
 
template<typename QueryScore , typename ReferenceScore >
__host__ __device__ bool wide_rows_aligned (QueryScore const *query, ReferenceScore const *reference) noexcept
 True when both row bases meet load_256_alignment on the compiled architecture.
 
template<typename T >
__device__ __forceinline__ void load_256_global_nc (const T *ptr, T *out)
 Loads 256 bits from global memory using the non-coherent cache path.
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > validate_score_compatibility (score_compatibility const &compatibility)
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > validate_non_indexed_score_compatibility (score_compatibility const &compatibility)
 
template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > validate_indexed_score_compatibility (score_compatibility const &compatibility)
 
constexpr uint64_t indexed_cell_count (score_compatibility const &compatibility) noexcept
 
constexpr uint64_t indexed_posting_count (uint32_t reference_count, score_compatibility const &compatibility) noexcept
 
constexpr index_storage smaller_index_storage (uint32_t reference_count, score_compatibility const &compatibility) noexcept
 Storage needing fewer device bytes for reference_count references; dense on ties.
 
uintptr_t align_up (uintptr_t address, size_t alignment) noexcept
 
constexpr uint32_t batch_query_tile_size (uint32_t reference_count, uint32_t query_count, uint32_t tile_limit=batch_query_tile_count) noexcept
 
constexpr uint32_t all_to_all_tile_queries (uint32_t reference_count, uint32_t parts=1U) noexcept
 Query rows one all-to-all tile compares: about 2^25 pairs, and at least batch_query_tile_count.
 
uint32_t external_batch_query_limit (uint32_t reference_count, cuda::device_ref device)
 Query rows one external batch tile may hold on device.
 
template<typename T >
T database_file_little_endian (T value)
 
template<typename IO >
Result< void > database_file_metadata (IO &io, reference_database_metadata &metadata, uint32_t &blacklist_count)
 
Result< uint64_t > reference_database_digest (reference_database_file const &db)
 
__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 size_t top_k_heap_words (uint32_t k) noexcept
 Words of shared memory one heap takes: k keys, then their k packed counts.
 
__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, and sifts it down.
 
template<bool Thresholded, bool UpperTriangle>
__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 reference of the word.
 
template<bool UpperTriangle>
__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.
 
__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.
 
template<typename Bits , typename Entry >
__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, in shared memory, which only this warp updates, so it needs no lock.
 
template<bool ToShared>
__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 the warp's lanes.
 
 if (query >=tile.query_count) return
 
 top_k_warp_copy< true > (slots, row, heap)
 
 top_k_warp_merge (heap, slots.k, words - cuda::std::min(skipped, words), [&](uint32_t word) { return top_k_word_bits< Thresholded, UpperTriangle >(tile, query, skipped+word, words);}, [&](uint32_t candidate, uint64_t &key, uint64_t &value) { auto const reference=skipped *32U+candidate;auto const stored=top_k_stored< UpperTriangle >(tile, query, reference);key=top_k_key(stored.unpack(tile.bucket_count), reference);value=stored.bits;})
 
 top_k_warp_copy< false > (slots, row, heap)
 
 if (block >=static_cast< uint64_t >(row_words) *word_count) return
 
 _Pragma ("unroll") for(uint32_t bit=0U
 

Variables

constexpr std::array< std::string_view, 22 > ablations
 Optimisations that the CUDDL_ABLATE environment variable can turn off in a build configured with -Dablations=true (CUDDL_ABLATIONS=1).
 
constexpr bool ablations_enabled = false
 
constexpr double hash_range = 0x1p64
 2^64, the range of the 64-bit hash used by the sketch.
 
constexpr size_t default_arena_ceiling = size_t{128} << 20
 Default arena ceiling.
 
constexpr size_t stager_arenas = 4
 Staging arenas the copy stream rotates through, so copies run ahead of the kernels that read earlier arenas while other work shares the device.
 
constexpr size_t stager_hold_slots = 16
 Host memory the device still has to read must stay alive: the transfer engine's read of a pageable buffer is not ordered with the host writes that follow it.
 
constexpr size_t packed_buffer_bytes = size_t{2} << 20
 Bytes reserved per loader buffer that holds packed bases: a bacterial genome's two-bit words, about 1.5 MB, fit.
 
constexpr uint32_t missing_cell = 0x00FFFFFFU
 Eight ambiguous bases: an encoder cell past the end of a sequence.
 
constexpr size_t parallel_read_depth = 64
 Reads a parallel file system needs in flight to give one node its bandwidth: on MOGON's Lustre, 16 threads read 2.9 GB/s of ~1 MB files and 64 threads 6.7 GB/s.
 
constexpr uint64_t seed = 42
 Domain separation constant folded into every packed k-mer before hashing.
 
constexpr uint64_t PRIME64_1 = 11400714785074694791ULL
 xxHash-64 round primes.
 
constexpr uint64_t PRIME64_2 = 14029467366897019727ULL
 
constexpr uint64_t PRIME64_3 = 1609587929392839161ULL
 
constexpr uint64_t PRIME64_4 = 9650029242287828579ULL
 
constexpr uint64_t PRIME64_5 = 2870177450012600261ULL
 
constexpr int32_t nlz_bins = 66
 NLZ histogram bins for the hybrid estimator, including the empty tier.
 
constexpr double dlc_blend_low = 0.088
 
constexpr double dlc_blend_high = 0.952
 
constexpr double dlc_min_fraction = 0.002
 
constexpr double dlc_info_power = 4.5
 
constexpr double hybrid_low = 0.2
 
constexpr double hybrid_high = 5.0
 
constexpr uint32_t ddl_cf_size = 13
 
constexpr uint32_t block_size = 256
 Threads per CTA for the single-pair and cardinality reduction kernels.
 
constexpr size_t maximum_batch_rows = (size_t{1} << 31) - 1U
 Stored sketches one batch launch may index, bounded by the CUDA grid dimension.
 
template<typename QueryScore , typename ReferenceScore >
constexpr uint32_t wide_chunk_buckets
 Buckets one contiguous chunk covers, for a query/reference score pair.
 
constexpr uint32_t shared_construction_block_size = 768
 Threads per CTA for the CTA-local construction kernel.
 
__global__ size_t input_size
 
__global__ size_t uint32_t * registers
 
__global__ size_t uint32_t bool vector_input
 
__global__ size_t uint32_t bool blacklist_view blacklist
 
__global__ size_t uint32_t bool blacklist_view bool IncludeCardinality
 
__global__ size_t uint32_t bool blacklist_view bool Layout
 
constexpr uint32_t index_match_cells_per_warp = 32U
 Cells one warp owns per iteration of count_batch_index_matches_kernel: one per lane.
 
__global__ uint32_t reference_count
 
__global__ uint32_t uint32_t indexed_bucket_count
 
__global__ uint32_t uint32_t uint32_t * directory
 
auto const shift = sparse_directory_shift(reference_count)
 
auto const total = static_cast<size_t>(indexed_bucket_count) * entries
 
__global__ uint16_t const * sorted_keys
 
__global__ uint16_t const uint32_t uint32_t uint32_t key_count = static_cast<uint32_t>(key_mask) + 1U
 
__global__ uint16_t const uint32_t uint32_t uint32_t unsigned long long * work
 
unsigned long long local = 0U
 
constexpr uint32_t posting_range_block_size = 1024U
 Threads of the single index_posting_ranges_kernel block.
 
static __global__ uint32_t const * offsets
 
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t key_mask
 
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t * range_begin
 
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t uint32_t * range_end_scan
 
__shared__ block_scan::TempStorage storage
 
uint32_t carry = 0U
 
static __global__ uint32_t const uint32_t uint32_t const * postings
 
static __global__ uint32_t const uint32_t uint32_t const uint32_t * match_counts
 
auto const stride = static_cast<uint32_t>(gridDim.x) * block_size
 
__global__ size_t query_row_offset
 
__global__ size_t uint32_t query_count
 
constexpr uint32_t index_tile_lane_postings = 16U
 Longest posting range one count_batch_index_tile_kernel lane walks alone.
 
constexpr uint32_t index_tile_long_capacity = 32U
 Long posting ranges one count_batch_index_tile_kernel block walks together.
 
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t tile
 
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const * ranges
 
__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 minimum_matches
 
__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
 
__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 bool all_to_all
 
__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 bool uint32_t * candidate_bits
 
__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 bool uint32_t uint32_t const * bitmaps
 
__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 bool uint32_t uint32_t const uint32_t const * bitmap_starts
 
__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 bool uint32_t uint32_t const uint32_t const uint32_t const * bitmap_counts
 
constexpr uint32_t warps_per_block = index_tile_block_size / warp_width
 
__shared__ uint32_t packed_counts []
 
auto const tiles = (reference_count + tile - 1U) / tile
 
auto const query_index = static_cast<uint32_t>(blockIdx.x) / tiles
 
auto low = static_cast<uint32_t>(blockIdx.x) % tiles * tile
 
auto const high = min(low + tile, reference_count)
 
auto const width = high - low
 
__shared__ uint2 long_ranges [index_tile_long_capacity]
 
__shared__ uint32_t long_count
 
auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width
 
auto const warp = static_cast<uint32_t>(threadIdx.x) / warp_width
 
 else
 
auto const * query = queries + (query_row_offset + query_index) * BucketCount
 
auto const count
 
auto const long_ranges_used
 
auto *const bits
 
constexpr size_t refine_block_row_bytes = size_t{8} << 20U
 Reference-row bytes one refine_batch_index_candidates_kernel block spans.
 
constexpr uint32_t score_planes = 16U
 Bits per score, and so bit-planes per 32-bucket group.
 
__global__ uint32_t row_count
 
__global__ uint32_t uint32_t * planes
 
constexpr uint32_t groups_per_row = static_cast<uint32_t>(BucketCount / warp_width)
 
__global__ size_t uint16_t * rows
 
__global__ uint32_t const * reference_planes
 
__global__ uint32_t const uint32_t uint32_t const * candidate_ids
 
__global__ uint32_t const uint32_t uint32_t const uint32_t const * candidate_count
 
__global__ uint32_t const uint32_t uint32_t const uint32_t const SearchResult * results
 
constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U)
 
auto const items = candidate_ids == nullptr ? reference_count : *candidate_count
 
auto const pairs = static_cast<uint64_t>(query_count) * reference_count
 
auto const indexed_groups = indexed_bucket_count / warp_width
 
constexpr uint32_t bitmap_refine_block_size = 768U
 Threads per refine_batch_bitmap_kernel block.
 
constexpr uint32_t bitmap_refine_waves = 8U
 Cells per refine_batch_bitmap_kernel block that a tile of bitmap-selected pairs is split into when it allows.
 
constexpr size_t bitmap_refine_shared_bytes = 96U * 1024U
 Shared bytes refine_batch_bitmap_kernel may stage query planes in; one block per SM fits on every supported architecture.
 
template<size_t BucketCount>
constexpr bool bitmap_refine_staged = bitmap_refine_shared_bytes >= BucketCount * sizeof(uint16_t)
 Whether a query group's planes fit bitmap_refine_shared_bytes.
 
template<size_t BucketCount>
constexpr uint32_t bitmap_refine_group
 Queries one refine_batch_bitmap_kernel block refines together.
 
template<size_t BucketCount>
constexpr size_t bitmap_refine_dynamic_bytes
 Shared bytes one refine_batch_bitmap_kernel block stages.
 
__global__ uint32_t const uint32_t const * pass_bits
 
__global__ uint32_t const uint32_t const uint32_t const * word_offsets
 
__global__ uint32_t const uint32_t const uint32_t const uint32_t first_query_id
 
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t word_begin
 
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t word_end
 
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t packed_base
 
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult * packed
 
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult uint32_t * packed_match_counts
 
auto const words_per_query = candidate_bit_words(reference_count)
 
__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t * result_match_counts
 
__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t uint32_t uint32_t uint32_t * cell_counter
 
constexpr uint32_t group = QueryGroup
 
constexpr bool staged = bitmap_refine_staged<BucketCount>
 
constexpr uint32_t lane_groups = groups_per_row / warp_width
 
constexpr uint32_t cached_groups = lane_groups < 2U ? lane_groups : 2U
 
constexpr uint32_t max_block_words
 
constexpr uint32_t word_slices = Candidates == refine_candidates::all ? 1U : 4U
 
__shared__ uint4 query_plane_storage []
 
__shared__ uint32_t next_word
 
__shared__ uint32_t claimed_cell
 
auto const cells
 
auto const groups = cells.groups
 
auto const thresholded = CountMatches && pass_bits != nullptr
 
constexpr uint32_t match_filter_block_size = 512U
 Threads per match_filter_kernel block.
 
template<size_t BucketCount>
constexpr bool match_filter_supported
 Whether match_filter_kernel supports BucketCount: a lane caches at most two plane groups of each of its references.
 
constexpr uint32_t match_filter_planes = 12U
 Low planes match_filter_kernel compares.
 
constexpr uint32_t all_pairs_block_size = 512U
 Threads per refine_all_pairs_kernel block.
 
constexpr uint32_t all_pairs_word_parts = 4U
 Parts a refine_all_pairs_kernel warp claims each 32-reference word in.
 
__global__ uint32_t uint32_t uint32_t SearchResult uint32_t const * reference_mask
 
auto const warps = uint64_t{gridDim.x} * (block_size / warp_width)
 
constexpr uint32_t delta_change_limit = 160U
 Most buckets in which a child reference may differ from its base reference.
 
constexpr uint32_t delta_signatures = 8U
 Exact bucket signatures that propose base candidates, and buckets per signature.
 
constexpr uint32_t delta_signature_buckets = 8U
 
constexpr uint32_t delta_signature_candidates = 4U
 Highest-ID rows taken from each matching signature.
 
constexpr uint32_t delta_candidates = delta_signatures * delta_signature_candidates
 
constexpr uint32_t delta_correction_warps = 24U
 Warps per apply_reference_deltas_kernel block, and the most and fewest children one block cell takes.
 
constexpr uint32_t delta_child_tile = 2048U
 
constexpr uint32_t delta_min_child_tile = 4U * delta_correction_warps
 
template<size_t BucketCount>
constexpr uint32_t delta_query_group = static_cast<uint32_t>(32768U / BucketCount)
 Queries one correction cell stages bucket-major: 64 KiB of scores for every bucket count.
 
template<size_t BucketCount>
constexpr size_t delta_correction_shared_bytes
 
__global__ uint32_t uint32_t signature
 
__global__ uint32_t uint32_t uint64_t * keys
 
__global__ uint32_t uint32_t uint64_t uint32_t * ids
 
constexpr uint8_t mantissa_bits = default_register_layout::mantissa_bits
 Default score layout retained for source compatibility.
 
constexpr uint32_t mantissa_mask = (1U << mantissa_bits) - 1U
 
constexpr uint32_t restore_shift_base = 63U - mantissa_bits
 Default restoration constants retained for source compatibility.
 
constexpr uint32_t max_nlz = restore_shift_base
 
constexpr double index_pair_fraction_limit = 0.03
 Index pair fraction (reference_index::pair_fraction) above which batch search compares bit-planes for every pair instead of counting through the index.
 
constexpr uint32_t batch_query_tile_count = 128U
 Minimum query rows per batch tile.
 
constexpr uint32_t top_k_warps = block_size / 32U
 Warps per merging block, each holding one heap; fewer when the heaps exceed shared memory.
 
__global__ top_k_slots slots
 
auto *const heap = heaps + warp * top_k_heap_words(slots.k)
 
auto const query_id = tile.first_query_id + query
 
auto const row = static_cast<size_t>(query_id - slots.first_genome_id)
 
auto const words = tile.words_per_query()
 
auto const skipped = UpperTriangle ? (query_id + 1U) / 32U : 0U
 
__global__ uint32_t * columns
 
__global__ uint32_t uint32_t first_word
 
auto const block = static_cast<uint64_t>(blockIdx.x) * (blockDim.x / 32U) + threadIdx.x / 32U
 
auto const word_count = words - first_word
 
auto const word = first_word + static_cast<uint32_t>(block % word_count)
 
auto const row_word = static_cast<uint32_t>(block / word_count)
 
uint32_t column = 0U
 

Typedef Documentation

◆ device_scope

using cuddl::detail::device_scope = typedef ::cuda::__ensure_current_context

Makes stream's device current for the enclosing scope and restores the previous context on exit.

Every public function that takes a stream opens one, so its kernel launches, CUB calls and function attributes target the stream's device, never the calling thread's current device. CCCL provides this only as an internal type.

Definition at line 55 of file reference_database.cuh.

◆ fastx_line_end_fn

Definition at line 175 of file fastx_sequence_file.hpp.

Enumeration Type Documentation

◆ decompression_backend

enum class cuddl::detail::decompression_backend : uint8_t
strong

Where inputs are decompressed; sketch construction always runs on the GPU.

Enumerator
automatic 
hybrid 

CPU inflation, followed by GPU FASTA normalisation of the inflated bytes.

cpu 

CPU inflation and normalisation.

gpu 

nvCOMP for eligible gzip inputs; unsupported formats use host loaders.

gpu_cores 

As gpu, with nvCOMP inflating on the SMs even where the device has a decompression engine: the baseline that shows what the engine adds.

Definition at line 46 of file database_staging.cuh.

46 : uint8_t {
49 hybrid,
51 cpu,
53 gpu,
57};
__device__ double hybrid(double low_estimate, double zone_estimate, double corrected_mean, double bucket_count) noexcept
Log-blended low and corrected estimates across the hybrid transition zone.
@ gpu
nvCOMP for eligible gzip inputs; unsupported formats use host loaders.
@ gpu_cores
As gpu, with nvCOMP inflating on the SMs even where the device has a decompression engine: the baseli...
@ cpu
CPU inflation and normalisation.

◆ fastx_format

enum class cuddl::detail::fastx_format : uint8_t
strong

Detected record format for a FASTX buffer.

Enumerator
unknown 

Format not yet determined from the first header.

fasta 

FASTA (> headers).

fastq 

FASTQ (@ headers).

Definition at line 438 of file fastx_sequence_file.hpp.

438 : uint8_t {
440 unknown,
442 fasta,
444 fastq,
445};
@ fasta
FASTA (> headers).
@ fastq
FASTQ (@ headers).
@ unknown
Format not yet determined from the first header.

◆ hybrid_variant

enum class cuddl::detail::hybrid_variant : uint8_t
strong

Which HybridDDL blend the variant kernel computes.

Enumerator
bbtools 
paper 

Definition at line 21 of file detail/hybrid_cardinality.cuh.

◆ pinned_placement

enum class cuddl::detail::pinned_placement : uint8_t
strong

Where a device_pinned_pool takes its pages.

Enumerator
device_node 

The device's host NUMA node, where the system supports NUMA pools: for buffers the device writes, such as downloaded results.

host 

Any node: for buffers host threads fill.

Sketch loaders run on every node, and on an A40 node with 16 NUMA nodes and two GPUs per node, device-node loader buffers sketched 100,000 genomes in 18.6 s against 15.1 s.

Definition at line 19 of file pinned_pool.cuh.

19 : uint8_t {
26 host,
27};
@ device_node
The device's host NUMA node, where the system supports NUMA pools: for buffers the device writes,...
@ host
Any node: for buffers host threads fill.

◆ refine_candidates

Pairs refine_batch_bitmap_kernel compares.

Enumerator
bitmap 

Exactly the pairs set in a pass bitmap.

all 

Every pair of the layout, or with pass bits requested, those meeting the threshold.

reference_mask 

Every pair of the layout whose reference is set in one reference-wide bitmap.

Definition at line 1737 of file kernels.cuh.

1737 {
1739 bitmap,
1741 all,
1744};
@ bitmap
Exactly the pairs set in a pass bitmap.
@ all
Every pair of the layout, or with pass bits requested, those meeting the threshold.
__global__ uint32_t uint32_t uint32_t SearchResult uint32_t const * reference_mask
Definition kernels.cuh:2492

◆ transfer_mode

enum class cuddl::detail::transfer_mode : uint8_t
strong

How a build gets CPU-parsed sequence bytes to the device.

Enumerator
automatic 

In-place host reads on coherent devices, page-locked transfers elsewhere.

in_place 

Decompress into a heap buffer and let the kernels read it there.

Only a device that reads pageable host memory can do this, and it pays neither the copy nor the page-locking.

pinned 

Decompress into page-locked buffers, which the transfer engine reads directly.

Measured 2.1x faster than staging on an x86 host with a discrete GPU, and slower on a coherent system, where the same mapping costs host writes more than the copy it removes.

staged 

Decompress into a heap buffer and copy it to the device.

On a coherent system that copy is a single-threaded bounce through the driver, which is what leaves 72 loaders parked while one core copies and the device waits for data.

Definition at line 60 of file database_staging.cuh.

60 : uint8_t {
69 pinned,
73 staged,
74};
@ pinned
Decompress into page-locked buffers, which the transfer engine reads directly.
@ in_place
Decompress into a heap buffer and let the kernels read it there.
constexpr bool staged
Definition kernels.cuh:1860

Function Documentation

◆ __launch_bounds__() [1/6]

template<size_t BucketCount, bool CountMatches, typename SearchResult , refine_candidates Candidates, bool UpperTriangle, uint32_t QueryGroup>
__global__ cuddl::detail::__launch_bounds__ ( bitmap_refine_block_size  ) const

Exactly compares query bit-planes with references, sharing each reference row across a query group.

Block cells are (reference block, group of bitmap_refine_group queries), reference-block major so resident blocks share reference rows through L2. A block stages its group's query bit-planes (score_plane_index) in shared memory when they fit; warps then claim 32-reference words and, for every reference any group query selects, compare the reference's bit-planes with each selecting query. A row thus crosses L2 once per group rather than once per pair.

Each written result lands at its batch_result_slot. Candidates selects which pairs are written: those in candidate_bits (one word per query and 32 references), or every pair of the layout. With pass_bits (counting kernels only), only the selected pairs with at least minimum_matches index matches are written and recorded in pass_bits, in the candidate-bitmap layout. In bitmap mode pass_bits must be candidate_bits, whose failing pairs' bits are cleared in place. With result_match_counts, results also get their index match counts, recomputed from the planes.

◆ __launch_bounds__() [2/6]

__global__ cuddl::detail::__launch_bounds__ ( block_size  ) const

Builds a sparse key directory: entry c of bucket b is the first position in b's sorted keys whose key is at least c << shift.

Marks the first position of every run of equal sorted keys with its index.

Turns per-pair match counts into the query-major candidate bitmap.

Counts the query's posting matches for every reference, one posting per thread.

Adds the squared length of every nonzero-key posting list to work.

Dense indexes pass their cell offsets (key_count cells per bucket); sparse indexes pass their per-bucket sorted keys instead, whose runs are the posting lists. The sum is the number of (reference, reference, bucket) triples sharing an index key: an index walks about work / reference_count postings per database-like query.

◆ __launch_bounds__() [3/6]

template<size_t BucketCount>
__global__ cuddl::detail::__launch_bounds__ ( block_size  ) const

Transposes compact score rows into the bit-plane layout of score_plane_index.

Transposes an all-to-all tile's pass bits from first_word on into per-genome columns: word w * reference_count + g of columns holds which of tile rows 32 w to 32 w + 31 pass with genome g.

Hashes signature signature of every row; rows are emitted in descending ID order so a stable sort keeps the highest IDs first within a signature.

Counts every query/reference pair's index matches from bit-planes, one warp per pair.

Recovers row-major scores from bit-plane rows, one warp per 32-bucket group.

Used when a search asks for index semantics without an index: the counts equal the ones the index postings would produce.

One warp per 32-by-32 bit block, which its lanes load a row each and turn with 32 ballots; neighbouring warps take neighbouring words of the same rows, so they share the rows' sectors, and every warp writes 32 adjacent words.

◆ __launch_bounds__() [4/6]

template<size_t BucketCount, typename QueryRow >
__global__ cuddl::detail::__launch_bounds__ ( block_size  ) const

Counts index matches for every query/reference pair in one tile.

One warp per tile row merges the row's passing pairs into the row genome's heap.

Packs a threshold tile's passing results in order.

Compares one query's bit-planes with every reference, or with the selected candidates.

Resolves every query's sparse posting range for one indexed bucket per block.

Each lane of a warp resolves the posting range of its own (query, bucket) cell, so a warp keeps 32 independent range lookups in flight. The warp then walks every non-empty range with a lane stride, so hot keys with long lists (the dominant cost on skewed rows) are consumed 32 postings at a time.

Block b owns bucket b, so its lookups share the bucket's directory and keys through L1. ranges is query-major, one entry per (query, indexed bucket).

One warp compares one reference; lane l takes groups l, l + 32, ... With candidate_ids, item i refines reference candidate_ids[i] for *candidate_count items; otherwise item i is reference i.

One warp per pass-bit word in [word_begin, word_end); lane l handles reference 32 * word + l, so consecutive lanes read consecutive slots and write consecutive packed positions. word_offsets is the exclusive scan of the word popcounts; results land at their packed position minus packed_base.

◆ __launch_bounds__() [5/6]

template<size_t BucketCount, typename SearchResult , bool UpperTriangle, bool Swar>
__global__ cuddl::detail::__launch_bounds__ ( block_size  ) const

The all-pairs comparison of refine_all_pairs_kernel over row-major 16-bit scores instead of bit-planes, one warp per pair, for ablation builds to compare with.

With Swar each lane compares 16 buckets per step from 256-bit loads, two packed scores per 32-bit subtraction (classify_query_words); without, it classifies one bucket at a time. rows holds every reference's row, and query q of the tile is row query_id_offset + q.

◆ __launch_bounds__() [6/6]

template<size_t BucketCount, typename Layout = default_register_layout, bool HasBlacklist = false>
__global__ cuddl::detail::__launch_bounds__ ( shared_construction_block_size  ) const

Constructs a sketch through a CTA-local shared-memory register array.

Each item costs a hash, a score, and one fire-and-forget shared atomic max; the CTA then merges its registers into the global sketch with one global atomic max per nonempty bucket.

The host launches two CTAs per SM and the kernel walks the input with a runtime grid-stride loop over 256-bit chunks, so the grid is a single balanced wave for every input size and the per-CTA merge traffic stays minimal.

◆ __maxnreg__()

template<size_t BucketCount, uint32_t CounterBits, typename QueryRow >
__global__ cuddl::detail::__maxnreg__ ( 48  ) const

Counts index matches for one query against one reference tile in shared memory.

Block b owns query b / tiles and references [t * tile, (t + 1) * tile) for t = b % tiles, with two 16-bit counters per shared word. A query's tiles run as neighbouring blocks, so they share its posting-range lookups through the caches. Postings ascend by reference within each key, so each posting list is narrowed to the tile with two binary searches. The flush writes every pair of the tile, so match_counts needs no zeroing. With nonzero CounterBits, each lane first sums the query's posting bitmaps over one word of the tile in that many bit-sliced counters, seeding references that already reach minimum_matches with exactly that count; the posting lists then add the rest.

Launched with index_tile_block_size threads: the shared counters, not the thread count, bound how many blocks fit on an SM, so larger blocks keep more warps resident to hide the posting loads' latency. Capping registers at 48 leaves a quarter of the SM's registers to kernels of neighbouring tiles; a GH200 searched 115,223 genomes 12% faster than with 60.

◆ __syncthreads()

cuddl::detail::__syncthreads ( )
Here is the caller graph for this function:

◆ _Pragma()

cuddl::detail::_Pragma ( "unroll"  )
pure virtual
Here is the caller graph for this function:

◆ ablated()

constexpr bool cuddl::detail::ablated ( std::string_view  )
constexprnoexcept

Always false: this build compiles the ablation switches out.

Definition at line 82 of file ablation.hpp.

82 {
83 return false;
84}
Here is the caller graph for this function:

◆ acquire_sequence_target()

decompression_target cuddl::detail::acquire_sequence_target ( void *  context,
size_t  bytes 
)
inline

decompression_source trampoline for sequence_buffer_pool.

Definition at line 1012 of file database_staging.cuh.

1012 {
1013 return static_cast<sequence_buffer_pool*>(context)->acquire(bytes);
1014}
Here is the caller graph for this function:

◆ add_sequence_batch_kernel()

template<size_t BucketCount, typename Layout , bool HasBlacklist = false, bool Naive = false>
__global__ void cuddl::detail::add_sequence_batch_kernel ( sequence_batch_chunk const *  chunks,
size_t  chunk_count,
size_t  block_count,
uint32_t  k,
uint32_t *  registers,
blacklist_view  blacklist = {} 
)

Batch kernel covering every staged chunk in one launch, including short records.

The register cap keeps the blacklist lookup from costing resident blocks: at 43 registers, a GH200 SM held five blocks instead of eight.

Definition at line 304 of file sequence_encode.cuh.

310 {}
311) {
312 __shared__ sequence_batch_chunk chunk;
313 __shared__ size_t first_block;
314 for (size_t block = blockIdx.x; block < block_count; block += gridDim.x) {
315 if (threadIdx.x == 0) {
316 size_t low = 0, high = chunk_count;
317 while (low < high) {
318 auto const middle = low + (high - low) / 2;
319 if (chunks[middle].block_end <= block) {
320 low = middle + 1;
321 } else {
322 high = middle;
323 }
324 }
325 chunk = chunks[low];
326 first_block = low ? chunks[low - 1].block_end : 0;
327 }
329 auto* target = registers + size_t{chunk.genome} * BucketCount;
330 add_sequence_windows<BucketCount, Layout, HasBlacklist, Naive>(
331 chunk.bases,
332 chunk.windows,
333 (block - first_block) * 2048,
334 (chunk.block_end - first_block) * 2048,
335 k,
336 target,
337 chunk.packed != 0,
338 blacklist
339 );
341 }
342}
__global__ size_t uint32_t * registers
Definition kernels.cuh:94

◆ add_sequence_single_kernel()

template<size_t BucketCount, typename Layout , bool HasBlacklist = false>
__global__ void cuddl::detail::add_sequence_single_kernel ( char const *  sequence,
uint32_t  windows,
uint32_t  k,
uint32_t *  registers,
blacklist_view  blacklist = {} 
)

Single-sequence kernel for the public raw-ASCII API.

There is no cross-chunk carry: the caller supplies any K-1 overlap explicitly.

Definition at line 350 of file sequence_encode.cuh.

355 {}
356) {
357 add_sequence_windows<BucketCount, Layout, HasBlacklist>(
358 sequence,
359 windows,
360 size_t{blockIdx.x} * 2048,
361 size_t{gridDim.x} * 2048,
362 k,
363 registers,
364 false,
366 );
367}
__global__ size_t uint32_t bool blacklist_view blacklist
Definition kernels.cuh:96

◆ add_sequence_windows()

template<size_t BucketCount, typename Layout , bool HasBlacklist = false, bool Naive = false>
__device__ __forceinline__ void cuddl::detail::add_sequence_windows ( char const *  sequence,
uint32_t  windows,
size_t  first_tile,
size_t  tile_stride,
uint32_t  k,
uint32_t *  registers,
bool  packed_input = false,
blacklist_view  blacklist = {} 
)

Encodes raw ASCII windows into registers using shared overlapping tiles.

Each shared word holds eight two-bit bases and eight ambiguity bits. Threads reuse neighboring words to construct eight overlapping windows on both strands. Naive instead builds every window base by base with a branch per base, for ablation studies only.

Definition at line 44 of file sequence_encode.cuh.

52 {}
53) {
54 constexpr uint32_t tile_size = 256 * 8;
55 __shared__ uint32_t cells[tile_size / 8 + 4];
56 __shared__ uint32_t local[BucketCount];
57 for (uint32_t i = threadIdx.x; i < BucketCount; i += blockDim.x) {
58 local[i] = 0;
59 }
60 uint32_t hash_ceiling = UINT32_MAX;
61 size_t tiles = 0;
62 auto const mask = (uint64_t{1} << (2 * k)) - 1;
63 auto const valid_mask = (uint32_t{1} << k) - 1;
64 for (size_t tile = first_tile; tile < windows; tile += tile_stride) {
65 auto const count = cuda::std::min(tile_size, windows - static_cast<uint32_t>(tile));
66 if constexpr (Naive) {
67 // The tiled path's cell fill ends with the barrier that orders the zeroing of
68 // @c local before its first update; this path has no fill, so it waits here.
70 auto const start = threadIdx.x * 8;
71 auto const* const words = reinterpret_cast<uint32_t const*>(sequence);
72 for (uint32_t i = start; i < cuda::std::min(start + 8U, count); ++i) {
73 uint64_t forward = 0, reverse = 0;
74 bool valid = true;
75 for (uint32_t j = 0; valid && j < k; ++j) {
76 auto const at = tile + i + j;
77 // Codes as the tiled path assigns them: A 0, C 1, T 2, G 3; complement ^ 2.
78 uint32_t code = 0;
79 if (packed_input) {
80 code = (words[at / 16] >> (2 * (at % 16))) & 3U;
81 } else {
82 switch (sequence[at]) {
83 case 'A':
84 case 'a':
85 code = 0;
86 break;
87 case 'C':
88 case 'c':
89 code = 1;
90 break;
91 case 'T':
92 case 't':
93 code = 2;
94 break;
95 case 'G':
96 case 'g':
97 code = 3;
98 break;
99 default:
100 valid = false;
101 }
102 }
103 forward = (forward << 2) | code;
104 reverse |= uint64_t{code ^ 2U} << (2 * j);
105 }
106 if (!valid) continue;
107 auto const word = forward > reverse ? forward : reverse;
108 auto const hash = hash_kmer(word);
109 if (static_cast<uint32_t>(hash >> 32) > hash_ceiling) continue;
110 auto const incoming = score<Layout>(hash);
111 auto const bucket = bucket_of<BucketCount>(hash);
112 if constexpr (HasBlacklist) {
113 if (incoming > load_shared_relaxed(&local[bucket]) &&
114 !blacklist.contains(word, hash, bucket)) {
115 atomicMax(&local[bucket], incoming);
116 }
117 } else {
118 atomicMax(&local[bucket], incoming);
119 }
120 }
121 } else {
122 // Four extra cells cover the k-1 halo, including the last partial cell.
123 auto const padded = (count + 7) / 8 + 4;
124 if (packed_input) {
125 // Two-bit words hold sixteen bases, base j in bits 2j, and no ambiguous base. Tiles
126 // start on a multiple of sixteen bases, so a tile's cells are whole halves of
127 // words.
128 auto const* const words = reinterpret_cast<uint32_t const*>(sequence);
129 auto const total = (static_cast<size_t>(windows) + k - 1 + 7) / 8;
130 for (uint32_t cell = threadIdx.x; cell < padded; cell += blockDim.x) {
131 auto const index = tile / 8 + cell;
132 if (index >= total) {
133 cells[cell] = missing_cell;
134 continue;
135 }
136 // A cell holds its first base in its highest bits: reverse the codes' order.
137 auto const reversed = __brev((words[index / 2] >> (16 * (index % 2))) << 16);
138 cells[cell] = ((reversed & 0x5555U) << 1) | ((reversed >> 1) & 0x5555U);
139 }
140 }
141 for (uint32_t cell = threadIdx.x; !packed_input && cell < padded; cell += blockDim.x) {
142 auto const pos = cell * 8;
143 uint32_t ascii[2] = {};
144 if ((reinterpret_cast<uintptr_t>(sequence + tile) & 7U) == 0 &&
145 pos + 7 < count + k - 1) {
146 auto const value = *reinterpret_cast<uint2 const*>(sequence + tile + pos);
147 ascii[0] = value.x;
148 ascii[1] = value.y;
149 } else {
150 _Pragma("unroll")
151 for (uint32_t j = 0; j < 8; ++j) {
152 auto const byte = pos + j < count + k - 1
153 ? static_cast<uint8_t>(sequence[tile + pos + j])
154 : uint8_t{0xFF};
155 ascii[j / 4] |= static_cast<uint32_t>(byte) << (8 * (j % 4));
156 }
157 }
158 uint32_t packed = 0, bad = 0;
159 _Pragma("unroll")
160 for (uint32_t j = 0; j < 2; ++j) {
161 // Only the T code differs in ASCII bits 0 and 4.
162 auto const thymine = (ascii[j] & ~(ascii[j] << 1) & 0x04040404U) >> 2;
163 auto const normalised = (ascii[j] ^ (thymine * 0x11U)) & 0xD9D9D9D9U;
164 auto const bad_bytes = __vsetne4(normalised, 0x41414141U);
165 auto const codes = (ascii[j] >> 1) & 0x03030303U;
166 // Gather four codes in reverse byte order and four invalid flags in byte order.
167 packed = __dp4a(codes, 0x01041040U, packed << 8);
168 bad = __dp4a(bad_bytes, 0x08040201U << (4 * j), bad);
169 }
170 cells[cell] = packed | (bad << 16);
171 }
173 auto const start = threadIdx.x * 8;
174 if (start < count) {
175 auto const cell = start / 8;
176 auto const a = cells[cell], b = cells[cell + 1];
177 auto const c = cells[cell + 2], d = cells[cell + 3];
178 auto high =
179 (uint64_t{a} << 48) | (uint64_t{b & 0xFFFFU} << 32) | (c << 16) | (d & 0xFFFFU);
180 uint64_t low = 0;
181 auto bad = uint64_t{a >> 16} | (uint64_t{b >> 16} << 8) |
182 (uint64_t{c >> 16} << 16) | (uint64_t{d >> 16} << 24);
183 if (k > 25) {
184 auto const e = cells[cell + 4];
185 low = uint64_t{e & 0xFFFFU} << 48;
186 bad |= uint64_t{e >> 16} << 32;
187 }
188 // Reverse the span once, then slide both strands across its eight windows.
189 auto reverse_word = [](uint64_t word) {
190 auto const bits = cuda::std::bit_reverse(word);
191 return (((bits & 0xAAAAAAAAAAAAAAAAULL) >> 1) |
192 ((bits & 0x5555555555555555ULL) << 1)) ^
193 0xAAAAAAAAAAAAAAAAULL;
194 };
195 auto reverse_high = reverse_word(high);
196 auto reverse_low = reverse_word(low);
197 // An invalid base in the common overlap excludes all eight windows.
198 auto const end = (bad & valid_mask & ~uint64_t{0x7F}) == 0
199 ? cuda::std::min(8U, count - start)
200 : 0U;
201 // Specialising on k > 25 keeps the slide branch-free; the compiler does not
202 // unswitch it once the blacklist lookup is present.
203 auto slide = [&]<bool Long>() {
204 _Pragma("unroll")
205 for (uint32_t i = 0; i < end; ++i) {
206 if ((bad & valid_mask) == 0) {
207 auto const forward = high >> (64 - 2 * k);
208 auto const reverse = reverse_high & mask;
209 auto const word = forward > reverse ? forward : reverse;
210 auto const hash = hash_kmer(word);
211 if (static_cast<uint32_t>(hash >> 32) <= hash_ceiling) {
212 auto const incoming = score<Layout>(hash);
213 auto const bucket = bucket_of<BucketCount>(hash);
214 // Look up only scores that can still raise the CTA's register.
215 if constexpr (HasBlacklist) {
216 if (incoming > load_shared_relaxed(&local[bucket]) &&
217 !blacklist.contains(word, hash, bucket)) {
218 atomicMax(&local[bucket], incoming);
219 }
220 } else {
221 atomicMax(&local[bucket], incoming);
222 }
223 }
224 }
225 // Eight overlapping windows fit in one 32-base word when k <= 25.
226 if constexpr (Long) {
227 high = (high << 2) | (low >> 62);
228 low <<= 2;
229 reverse_high = (reverse_high >> 2) | (reverse_low << 62);
230 reverse_low >>= 2;
231 } else {
232 high <<= 2;
233 reverse_high >>= 2;
234 }
235 bad >>= 1;
236 }
237 };
238 if (k > 25) {
239 slide.template operator()<true>();
240 } else {
241 slide.template operator()<false>();
242 }
243 }
244 }
246 // Share accepted winners across CTAs before processing the remaining tiles.
247 if ((++tiles == 4 || tiles % 16 == 0) && tile + tile_stride < windows) {
248 using reduce = cub::BlockReduce<uint32_t, 256>;
249 __shared__ typename reduce::TempStorage exchange_scratch;
250 __shared__ uint32_t exchange_ceiling;
251 uint32_t minimum = 0xffffU;
252 // Each thread owns its buckets here, so the merged value feeds the floor directly.
253 for (uint32_t b = threadIdx.x; b < BucketCount; b += blockDim.x) {
254 auto const value = local[b];
255 auto global =
256 cuda::atomic_ref<uint32_t, cuda::thread_scope_device>{registers[b]}.load(
257 cuda::memory_order_relaxed
258 );
259 if (value > global) global = cuda::std::max(value, atomicMax(&registers[b], value));
260 local[b] = global;
261 minimum = cuda::std::min(minimum, global);
262 }
263 auto const result = reduce(exchange_scratch).Reduce(minimum, cuda::minimum<>{});
264 if (threadIdx.x == 0) {
265 // An inclusive high-word cutoff admits ties for exact per-bucket pruning.
266 exchange_ceiling =
267 result == 0 ? UINT32_MAX : uint32_t(restore<Layout>(result) >> 32);
268 }
270 hash_ceiling = exchange_ceiling;
271 }
272 }
274 for (uint32_t i = threadIdx.x; i < BucketCount; i += blockDim.x) {
275 if (local[i] != 0U) atomicMax(&registers[i], local[i]);
276 }
277}
_Pragma("unroll") for(uint32_t bit=0U
auto const high
Definition kernels.cuh:940
auto const cells
Definition kernels.cuh:1878
constexpr uint32_t missing_cell
Eight ambiguous bases: an encoder cell past the end of a sequence.
Definition dna.hpp:27
auto const count
Definition kernels.cuh:1031
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t tile
Definition kernels.cuh:922
__device__ __forceinline__ uint32_t load_shared_relaxed(uint32_t const *address)
Relaxed CTA-scope load of a shared word that other threads update atomically.
unsigned long long local
Definition kernels.cuh:478
auto const total
Definition kernels.cuh:443
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult * packed
Definition kernels.cuh:1791
__host__ __device__ constexpr uint64_t hash_kmer(uint64_t packed_kmer) noexcept
Hashes a canonical packed k-mer into the value space used by DDL registers.
Definition hash.cuh:21
auto const words
Definition top_k.cuh:204
auto *const bits
Definition kernels.cuh:1184
auto const word
Definition top_k.cuh:241
__device__ bool contains(uint64_t key, uint64_t hash, size_t bucket) const noexcept
hash must be hash_kmer(key); construction already holds it.
Definition blacklist.cuh:50

◆ agree()

Result< void > cuddl::detail::agree ( Result< void >  local,
MPI_Comm  comm 
)
inline

Returns local on the rank that failed, and on every other rank an error naming the lowest failing rank, so that all ranks leave a collective step together.

Definition at line 75 of file distributed.cuh.

75 {
76 int rank = 0, size = 1;
77 CUDDL_TRY(mpi_try(MPI_Comm_rank(comm, &rank), "MPI_Comm_rank"));
78 CUDDL_TRY(mpi_try(MPI_Comm_size(comm, &size), "MPI_Comm_size"));
79 int const mine = local ? size : rank;
80 int failed = size;
81 CUDDL_TRY(mpi_try(MPI_Allreduce(&mine, &failed, 1, MPI_INT, MPI_MIN, comm), "MPI_Allreduce"));
82 if (!local) return local;
83 if (failed == size) return Ok();
84 return Err(Error::resource("rank " + std::to_string(failed) + " failed"));
85}
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:225
Result< void > mpi_try(int status, char const *call)
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
Here is the call graph for this function:
Here is the caller graph for this function:

◆ align_up()

uintptr_t cuddl::detail::align_up ( uintptr_t  address,
size_t  alignment 
)
inlinenoexcept

Definition at line 944 of file reference_database.cuh.

944 {
945 return (address + alignment - 1U) & ~(static_cast<uintptr_t>(alignment) - 1U);
946}
Here is the caller graph for this function:

◆ all_to_all_tile_queries()

constexpr uint32_t cuddl::detail::all_to_all_tile_queries ( uint32_t  reference_count,
uint32_t  parts = 1U 
)
constexprnoexcept

Query rows one all-to-all tile compares: about 2^25 pairs, and at least batch_query_tile_count.

Enough query groups to fill the GPU for each launch, while result storage holds two tiles so one is searched as the other is consumed.

Split into parts shards, tiles shrink until each part gets about four, so the alternating assignment evens out the triangle. They shrink only when the whole search already spans several tiles: its two-slot result storage then holds the smaller tiles too.

Definition at line 977 of file reference_database.cuh.

977 {
978 constexpr uint32_t pair_budget = 1U << 25U;
979 constexpr uint64_t tiles_per_part = 4U;
980 auto const limit = reference_count == 0U
982 : std::max(batch_query_tile_count, pair_budget / reference_count);
983 auto const whole = batch_query_tile_size(reference_count, reference_count, limit);
984 if (parts < 2U || whole == 0U || whole >= reference_count) return whole;
985 auto const per_part = static_cast<uint32_t>(reference_count / (parts * tiles_per_part));
986 return std::min(whole, std::max(batch_query_tile_count, per_part));
987}
__global__ uint32_t reference_count
Definition kernels.cuh:437
constexpr uint32_t batch_query_tile_count
Minimum query rows per batch tile.
constexpr uint32_t batch_query_tile_size(uint32_t reference_count, uint32_t query_count, uint32_t tile_limit=batch_query_tile_count) noexcept
Here is the call graph for this function:
Here is the caller graph for this function:

◆ available_device_bytes()

Result< size_t > cuddl::detail::available_device_bytes ( cuda::stream_ref  stream)
inline

Device bytes an allocation on stream can still get.

Storage the default pool caches from earlier allocations is reused by the next one, so it counts as available alongside what the device reports free.

Definition at line 127 of file database_staging.cuh.

127 {
128 size_t free_bytes = 0, device_bytes = 0;
129 CUDDL_CUDA_TRY(cudaMemGetInfo(&free_bytes, &device_bytes));
130 auto const& device_pool = cuda::device_default_memory_pool(stream.device());
131 auto const pool_reserved =
132 device_pool.attribute(cuda::memory_pool_attributes::reserved_mem_current);
133 auto const pool_used = device_pool.attribute(cuda::memory_pool_attributes::used_mem_current);
134 return free_bytes + pool_reserved - std::min(pool_reserved, pool_used);
135}
#define CUDDL_CUDA_TRY(...)
Propagates a CUDA error wrapped in cuddl::Result<void>.
Definition error.hpp:248
Here is the caller graph for this function:

◆ batch_cardinality_kernel()

template<size_t BucketCount, typename Layout = default_register_layout>
__global__ void cuddl::detail::batch_cardinality_kernel ( uint32_t const *const  registers,
uint32_t const  row_count,
uint64_t *const  empty_out,
double *const  estimates_out 
)

Computes the cardinality of every stored sketch in one launch.

One block reduces one row of the row-major store of BucketCount registers per sketch.

Definition at line 2649 of file kernels.cuh.

2654 {
2655 auto const row = static_cast<size_t>(blockIdx.x);
2656 if (row >= row_count) {
2657 return;
2658 }
2659 using block_reduce = cub::BlockReduce<cardinality_payload, block_size>;
2660 __shared__ typename block_reduce::TempStorage storage;
2661 auto const* const mine = registers + row * BucketCount;
2662 cardinality_payload local{};
2663 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2664 bucket += blockDim.x) {
2665 auto const stored = static_cast<uint16_t>(__ldcs(&mine[bucket]));
2666 if (stored == 0U) {
2667 ++local.empty;
2668 } else {
2669 local.restored += static_cast<float>(restore_midpoint<Layout>(stored));
2670 }
2671 }
2672 auto const total = block_reduce(storage).Sum(local);
2673 if (threadIdx.x == 0) {
2674 empty_out[row] = total.empty;
2675 estimates_out[row] = static_cast<double>(cardinality_f32(
2676 static_cast<float>(BucketCount), static_cast<float>(total.empty), total.restored
2677 ));
2678 }
2679}
__host__ __device__ float cardinality_f32(float bucket_count, float empty_count, float sum_restored) noexcept
Single-precision cardinality for the throughput-bound cardinality reduction.
auto const row
Definition top_k.cuh:202
Here is the call graph for this function:

◆ batch_query_tile_size()

constexpr uint32_t cuddl::detail::batch_query_tile_size ( uint32_t  reference_count,
uint32_t  query_count,
uint32_t  tile_limit = batch_query_tile_count 
)
constexprnoexcept

Definition at line 957 of file reference_database.cuh.

961 {
962 if (reference_count == 0U || query_count == 0U) {
963 return 0U;
964 }
965 auto const count_capacity = std::numeric_limits<uint32_t>::max() / reference_count;
966 return std::min(query_count, std::min(tile_limit, std::max(1U, count_capacity)));
967}
Here is the caller graph for this function:

◆ batch_result_slot()

template<bool UpperTriangle>
__host__ __device__ uint64_t cuddl::detail::batch_result_slot ( uint32_t  query_index,
uint32_t  reference_id,
uint32_t  query_id_offset,
uint32_t  reference_count 
)
inlinenoexcept

Position of the pair (query_index of the tile, reference_id) in a tile's results: rows of reference_count references per query, or for all-to-all tiles the packed strict upper triangle of pairs with query_id < reference_id, where query IDs start at query_id_offset.

Definition at line 1751 of file kernels.cuh.

1756 {
1757 if constexpr (UpperTriangle) {
1758 auto const query = static_cast<uint64_t>(query_index);
1759 auto const preceding = query * (reference_count - query_id_offset - 1U) -
1760 query * (query == 0U ? 0U : query - 1U) / 2U;
1761 return preceding + reference_id - (query_id_offset + query_index) - 1U;
1762 } else {
1763 return static_cast<uint64_t>(query_index) * reference_count + reference_id;
1764 }
1765}
auto const * query
Definition kernels.cuh:1030
auto const query_index
Definition kernels.cuh:938
__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
Definition kernels.cuh:927

◆ batch_scores_kernel()

template<size_t BucketCount>
__global__ void cuddl::detail::batch_scores_kernel ( uint32_t const *const  registers,
uint32_t const  row_count,
uint16_t *const  scores_out 
)

Copies the winning score of every register into compact row-major scores.

Definition at line 2683 of file kernels.cuh.

2687 {
2688 auto const row = static_cast<size_t>(blockIdx.x);
2689 if (row >= row_count) {
2690 return;
2691 }
2692 auto const* const mine = registers + row * BucketCount;
2693 auto* const scores = scores_out + row * BucketCount;
2694 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2695 bucket += blockDim.x) {
2696 scores[bucket] = static_cast<uint16_t>(__ldcs(&mine[bucket]));
2697 }
2698}

◆ bbtools_mean_m()

__device__ double cuddl::detail::bbtools_mean_m ( double  bucket_count,
double  filled,
double  restored_sum 
)
inlinenoexcept

BBTools occupancy-corrected MeanM estimate from restored magnitudes.

Definition at line 163 of file detail/hybrid_cardinality.cuh.

163 {
164 if (filled <= 0.0 || restored_sum <= 0.0) {
165 return 0.0;
166 }
167 auto const mean = restored_sum / filled;
168 auto const occupancy_correction = (filled + bucket_count) / (2.0 * bucket_count);
169 return hash_range / mean * filled * occupancy_correction;
170}
Here is the caller graph for this function:

◆ bbtools_mean_m_f32()

__device__ float cuddl::detail::bbtools_mean_m_f32 ( float  bucket_count,
float  filled,
float  restored_sum 
)
inlinenoexcept

Single-precision bbtools_mean_m for the latency-bound hybrid scan.

Definition at line 305 of file detail/hybrid_cardinality.cuh.

305 {
306 if (filled <= 0.0f || restored_sum <= 0.0f) {
307 return 0.0f;
308 }
309 auto const mean = restored_sum / filled;
310 auto const occupancy_correction = (filled + bucket_count) / (2.0f * bucket_count);
311 return static_cast<float>(hash_range) / mean * filled * occupancy_correction;
312}
Here is the caller graph for this function:

◆ bgzf_block_size()

uint32_t cuddl::detail::bgzf_block_size ( unsigned char const *  header)
inlinenoexcept

A BGZF block's total size from its 18-byte header, or 0 if it is not one: a gzip header with only FEXTRA set, holding just the BC subfield.

Definition at line 76 of file device_gzip.cuh.

76 {
77 unsigned char const expected[16] = {0x1f, 0x8b, 8, 4, 0, 0, 0, 0, 0, 0, 6, 0, 'B', 'C', 2, 0};
78 for (size_t i = 0; i < sizeof(expected); ++i) {
79 // Bytes 4-9 are MTIME, XFL and OS, which may hold anything.
80 if ((i < 4 || i > 9) && header[i] != expected[i]) return 0;
81 }
82 return (uint32_t{header[16]} | uint32_t{header[17]} << 8U) + 1U;
83}
Here is the caller graph for this function:

◆ bucket_of()

template<size_t BucketCount>
__host__ __device__ constexpr size_t cuddl::detail::bucket_of ( uint64_t  hash)
constexprnoexcept

Selects the register bucket for hash given a power-of-two BucketCount.

Definition at line 27 of file hash.cuh.

27 {
28 static_assert(BucketCount != 0 && (BucketCount & (BucketCount - 1)) == 0);
29 return hash & (BucketCount - 1);
30}

◆ cardinality()

__host__ __device__ double cuddl::detail::cardinality ( double  bucket_count,
double  empty_count,
double  sum_restored 
)
inlinenoexcept

Public DDL cardinality estimate.

Minimum MLE is the current production estimator. Keeping this dispatch point makes changing the estimator explicit without exposing implementation-specific cardinality APIs.

Definition at line 59 of file cardinality.cuh.

59 {
60 return minimum_mle(bucket_count, empty_count, sum_restored);
61}
__host__ __device__ double minimum_mle(double bucket_count, double empty_count, double sum_restored) noexcept
Estimates the number of distinct hashes from the minimum hash in each bucket.
Here is the call graph for this function:

◆ cardinality_f32()

__host__ __device__ float cuddl::detail::cardinality_f32 ( float  bucket_count,
float  empty_count,
float  sum_restored 
)
inlinenoexcept

Single-precision cardinality for the throughput-bound cardinality reduction.

Definition at line 91 of file cardinality.cuh.

91 {
92 return minimum_mle_f32(bucket_count, empty_count, sum_restored);
93}
__host__ __device__ float minimum_mle_f32(float bucket_count, float empty_count, float sum_restored) noexcept
Single-precision variants of the cardinality estimators.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ cardinality_kernel()

template<size_t BucketCount, typename Layout = default_register_layout>
__global__ void cuddl::detail::cardinality_kernel ( uint32_t const *  registers,
uint64_t *  empty_out,
double *  estimate_out 
)

Computes the cardinality of a single constructed sketch.

empty_out receives the empty-register count; estimate_out receives the estimate.

Definition at line 2551 of file kernels.cuh.

2551 {
2552 using block_reduce = cub::BlockReduce<cardinality_payload, block_size>;
2553 __shared__ typename block_reduce::TempStorage storage;
2555 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2556 bucket += blockDim.x) {
2557 auto const stored = static_cast<uint16_t>(__ldcs(&registers[bucket]));
2558 if (stored == 0U) {
2559 ++local.empty;
2560 } else {
2561 local.restored += static_cast<float>(restore_midpoint<Layout>(stored));
2562 }
2563 }
2564 auto const total = block_reduce(storage).Sum(local);
2565 if (threadIdx.x == 0) {
2566 *empty_out = total.empty;
2567 *estimate_out = static_cast<double>(cardinality_f32(
2568 static_cast<float>(BucketCount), static_cast<float>(total.empty), total.restored
2569 ));
2570 }
2571}
__shared__ block_scan::TempStorage storage
Definition kernels.cuh:545
Per-thread accumulator feeding the CUB block reduction for cardinality.
Definition kernels.cuh:2533
Here is the call graph for this function:

◆ classify()

__device__ void cuddl::detail::classify ( pairwise_counts &  target,
uint32_t  this_score,
uint32_t  other_score 
)
inlinenoexcept

Classifies one score relative to this into target.

Takes 16-bit scores and 32-bit registers alike. Branchless: all four outcomes are computed as predicated arithmetic so a warp with mixed comparisons (random sketch data) never pays branch-reconvergence costs. Both-empty is the equality outcome restricted to zero scores.

Definition at line 16 of file comparison.cuh.

16 {
17 auto const this_zero = static_cast<uint32_t>(this_score == 0U);
18 auto const lt = static_cast<uint32_t>(this_score < other_score);
19 auto const gt = static_cast<uint32_t>(this_score > other_score);
20 auto const eq = 1U - lt - gt;
21 target.both_empty += eq & this_zero;
22 target.lower += lt;
23 target.higher += gt;
24 target.equal += eq & (1U - this_zero);
25}
uint32_t both_empty
Both registers are empty in that bucket.
uint32_t equal
this register equals the other's in that bucket.
uint32_t lower
this register is strictly lower than the other's in that bucket.
uint32_t higher
this register is strictly higher than the other's in that bucket.
Here is the caller graph for this function:

◆ classify_query_words()

template<typename ReferenceScore >
__device__ __forceinline__ void cuddl::detail::classify_query_words ( pairwise_counts &  target,
uint32_t const (&)  q[8],
ReferenceScore const *  reference 
)
noexcept

Classifies 16 buckets of an already loaded 16-bit query chunk against one load_256_alignment aligned reference chunk.

Definition at line 1241 of file kernels.cuh.

1245 {
1246 auto const* qs = reinterpret_cast<uint16_t const*>(q);
1247 if constexpr (sizeof(ReferenceScore) == 2U) {
1248 uint32_t r[8];
1249 load_256_global_nc(reinterpret_cast<uint32_t const*>(reference), r);
1250 // Differences of zero-extended 16-bit scores carry the comparison in their sign bit,
1251 // and a sum of two scores minus one is negative only when both are empty. Equal is
1252 // whatever remains of the chunk's 16 buckets.
1253 uint32_t lower = 0U;
1254 uint32_t higher = 0U;
1255 uint32_t empty = 0U;
1256 _Pragma("unroll")
1257 for (uint32_t i = 0; i < 8U; ++i) {
1258 auto const q_low = q[i] & 0xffffU;
1259 auto const q_high = q[i] >> 16U;
1260 auto const r_low = r[i] & 0xffffU;
1261 auto const r_high = r[i] >> 16U;
1262 lower += ((q_low - r_low) >> 31U) + ((q_high - r_high) >> 31U);
1263 higher += ((r_low - q_low) >> 31U) + ((r_high - q_high) >> 31U);
1264 empty += ((q_low + r_low - 1U) >> 31U) + ((q_high + r_high - 1U) >> 31U);
1265 }
1266 target.lower += lower;
1267 target.higher += higher;
1268 target.both_empty += empty;
1269 target.equal += 16U - lower - higher - empty;
1270 } else {
1271 uint32_t r0[8];
1272 uint32_t r1[8];
1273 load_256_global_nc(reinterpret_cast<uint32_t const*>(reference), r0);
1274 load_256_global_nc(reinterpret_cast<uint32_t const*>(reference) + 8U, r1);
1275 _Pragma("unroll")
1276 for (uint32_t i = 0; i < 8U; ++i) {
1277 classify(target, qs[i], r0[i]);
1278 }
1279 _Pragma("unroll")
1280 for (uint32_t i = 0; i < 8U; ++i) {
1281 classify(target, qs[8U + i], r1[i]);
1282 }
1283 }
1284}
__device__ void classify(pairwise_counts &target, uint32_t this_score, uint32_t other_score) noexcept
Classifies one score relative to this into target.
__device__ __forceinline__ void load_256_global_nc(const T *ptr, T *out)
Loads 256 bits from global memory using the non-coherent cache path.
Definition helpers.cuh:47
Here is the call graph for this function:

◆ compact_fastx_sequence_extents()

void cuddl::detail::compact_fastx_sequence_extents ( fastx_sequence_file &  result)
inline

Compacts sequence whitespace in place, leaving every extent a run of bases.

Consumers stage extents verbatim and choose piece boundaries on any base, which needs contiguous bases. Compacting here moves the work onto the loader thread: each file is compacted while the other loaders are still decompressing, and a consumer that used to skip whitespace byte by byte now walks a dense span.

Definition at line 1413 of file fastx_sequence_file.hpp.

1413 {
1414 if (result.extents.empty()) return;
1415 // Extents point into whichever buffer holds the bytes: a page-locked target, the heap copy of
1416 // a compressed file, or the FASTQ sequence buffer. Those are writable; a memory-mapped plain
1417 // file is not, so its bases are compacted into `decompressed` instead.
1418 struct writable_buffer {
1419 char* data;
1420 size_t size;
1421 };
1422 auto const buffers = std::array{
1423 writable_buffer{result.decompressed_target, result.decompressed_size},
1424 writable_buffer{result.decompressed.data(), result.decompressed.size()},
1425 writable_buffer{result.sequence.data(), result.sequence.size()},
1426 };
1427 auto const writable = [&](char const* first, size_t size) {
1428 return std::any_of(buffers.begin(), buffers.end(), [&](writable_buffer const& buffer) {
1429 return buffer.data != nullptr && buffer.size >= size && first >= buffer.data &&
1430 first + size <= buffer.data + buffer.size;
1431 });
1432 };
1433 size_t raw_total = 0;
1434 for (auto const& extent : result.extents)
1435 raw_total += static_cast<size_t>(extent.end - extent.begin);
1436 // Reserved up front so appending cannot move the buffer the extents are about to point at.
1437 std::string copied;
1438 copied.reserve(raw_total);
1439 std::vector<std::pair<size_t, std::pair<size_t, size_t>>> offsets;
1440 for (size_t index = 0; index < result.extents.size(); ++index) {
1441 auto const& extent = result.extents[index];
1442 auto const size = static_cast<size_t>(extent.end - extent.begin);
1443 if (writable(extent.begin, size)) {
1444 // Compaction only ever moves bytes towards the buffer's start.
1445 auto* const write = compact_sequence_whitespace(
1446 extent.begin, extent.end, const_cast<char*>(extent.begin)
1447 );
1448 result.extents[index] = {extent.begin, write};
1449 } else {
1450 auto const begin = copied.size();
1451 // Sized before the copy so the write needs no bounds check per byte.
1452 copied.resize(begin + size);
1453 auto* const write =
1454 compact_sequence_whitespace(extent.begin, extent.end, copied.data() + begin);
1455 copied.resize(static_cast<size_t>(write - copied.data()));
1456 offsets.emplace_back(index, std::pair{begin, copied.size()});
1457 }
1458 }
1459 if (!copied.empty()) {
1460 result.decompressed = std::move(copied);
1461 for (auto const& [index, span] : offsets) {
1462 result.extents[index] = {
1463 result.decompressed.data() + span.first, result.decompressed.data() + span.second
1464 };
1465 }
1466 }
1467}
char * compact_sequence_whitespace(char const *first, char const *last, char *out)
Copies bases from [first, last) to out, dropping sequence whitespace.
static __global__ uint32_t const * offsets
Definition kernels.cuh:536
std::vector< fastx_sequence_extent > extents
Here is the call graph for this function:
Here is the caller graph for this function:

◆ compact_sequence_whitespace()

char * cuddl::detail::compact_sequence_whitespace ( char const *  first,
char const *  last,
char *  out 
)
inline

Copies bases from [first, last) to out, dropping sequence whitespace.

A sequence line is a long run of bases closed by a line ending, so a test per byte is the bulk of loading a corpus. Whole words are copied while none of their bytes is whitespace, and only a word that holds one falls back to bytes, which leaves the common case at one store per eight bases and keeps out no further along than the word being read.

Returns
The end of the written span.

Definition at line 1186 of file fastx_sequence_file.hpp.

1186 {
1187 if (static_cast<size_t>(last - first) >= 64 && simd_compaction) {
1188#if defined(__ARM_NEON)
1189 return compact_sequence_whitespace_neon(first, last, out);
1190#elif defined(__x86_64__) || defined(__i386__)
1191 if (compact_avx512) return compact_sequence_whitespace_avx512(first, last, out);
1192 if (compact_avx2) {
1193 return compact_sequence_whitespace_avx2(first, last, out);
1194 }
1195#endif
1196 }
1197 constexpr uint64_t ones = 0x0101010101010101ULL;
1198 auto const has_zero_byte = [](uint64_t word) {
1199 return (word - ones) & ~word & 0x8080808080808080ULL;
1200 };
1201 auto const has_byte = [&](uint64_t word, uint64_t byte) {
1202 return has_zero_byte(word ^ (ones * byte));
1203 };
1204 auto const* word = first;
1205 while (static_cast<size_t>(last - word) >= sizeof(uint64_t)) {
1206 uint64_t value = 0;
1207 std::memcpy(&value, word, sizeof(value));
1208 auto const whitespace = has_byte(value, ' ') | has_byte(value, '\t') |
1209 has_byte(value, '\n') | has_byte(value, '\r');
1210 if (whitespace == 0) {
1211 std::memcpy(out, word, sizeof(value));
1212 out += sizeof(value);
1213 } else {
1214 for (size_t index = 0; index < sizeof(value); ++index) {
1215 if (!fastx_is_sequence_whitespace(word[index])) *out++ = word[index];
1216 }
1217 }
1218 word += sizeof(value);
1219 }
1220 for (auto const* at = word; at != last; ++at) {
1221 if (!fastx_is_sequence_whitespace(*at)) *out++ = *at;
1222 }
1223 return out;
1224}
constexpr bool fastx_is_sequence_whitespace(char ch) noexcept
True for bytes that FASTA sequence consumers conventionally skip between bases.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ compare_plane_group()

template<bool CountMatches, bool ReferenceFull = false>
__device__ __forceinline__ void cuddl::detail::compare_plane_group ( uint32_t const (&)  q[score_planes],
uint32_t const (&)  r[score_planes],
uint32_t  reference_any,
uint32_t  reference_low_any,
uint16_t  key_mask,
uint32_t  match_lanes,
plane_counts &  counts 
)
noexcept

Adds one group's comparison of query planes q with reference planes r, whose nonzero buckets reference_any the caller hoists out of per-query loops.

From the least significant plane up, each plane's verdict overrides the lower planes' unless its bits are equal: one 3-input logic operation per plane for lt, one for ne. With count_matches, buckets whose masked keys agree and whose scores are both nonzero are counted too, as the index counts them.

With CountMatches, matches are counted in the buckets of match_lanes; the two stored key masks come from the plane chain: a full key matches exactly the equal nonempty buckets, and the folded 15-bit key those whose low planes agree and that are nonempty in both rows, which needs the reference's low-plane nonzero mask reference_low_any. Only those two masks pass score_compatibility validation.

ReferenceFull asserts every reference score is nonzero, so no bucket is both empty.

Definition at line 1457 of file kernels.cuh.

1465 {
1466 uint32_t lt = 0U;
1467 uint32_t ne = 0U;
1468 uint32_t low_ne = 0U;
1469 _Pragma("unroll")
1470 for (uint32_t p = 0; p < score_planes; ++p) {
1471 if (p == score_planes - 1U) {
1472 low_ne = ne;
1473 }
1474 lt = (~q[p] & r[p]) | (~(q[p] ^ r[p]) & lt);
1475 ne |= q[p] ^ r[p];
1476 }
1477 counts.lower += static_cast<uint32_t>(__popc(lt));
1478 counts.differ += static_cast<uint32_t>(__popc(ne));
1479 if constexpr (!ReferenceFull) {
1480 counts.occupied += static_cast<uint32_t>(__popc(ne | reference_any));
1481 }
1482 if constexpr (CountMatches) {
1483 // Validated key masks cover the low planes and, for a full key, the top one. Where the
1484 // compared planes agree, the low planes agree, so both rows are nonzero exactly when the
1485 // shared low planes are or both top bits are set.
1486 constexpr auto top = score_planes - 1U;
1487 auto const top_keyed = 0U - (static_cast<uint32_t>(key_mask) >> top);
1488 auto const key_differs = (ne & top_keyed) | (low_ne & ~top_keyed);
1489 counts.matches += static_cast<uint32_t>(
1490 __popc(~key_differs & (reference_low_any | (q[top] & r[top])) & match_lanes)
1491 );
1492 }
1493}
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t key_mask
Definition kernels.cuh:540
constexpr uint32_t score_planes
Bits per score, and so bit-planes per 32-bucket group.
Definition kernels.cuh:1326
@ 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:

◆ constexpr()

if cuddl::detail::constexpr ( CounterBits !  = 0U)
constexpr

Definition at line 968 of file kernels.cuh.

968 {
969 // Each lane sums one word's bitmaps; half warps then expand two words at a time, so the
970 // counter stores stay free of bank conflicts.
971 auto const tile_words = (width + warp_width - 1U) / warp_width;
972 auto const count = bitmap_counts[query_index];
973 auto const* starts =
974 bitmap_starts + static_cast<size_t>(query_index) * indexed_bucket_count;
975 for (auto base = warp * warp_width; base < tile_words; base += blockDim.x) {
976 auto const word = low / warp_width + base + lane;
977 uint32_t counters[CounterBits]{};
978 uint32_t passed = ~0U;
979 if (base + lane < tile_words) {
980 // References on or below the diagonal count as passed so that summing can stop
981 // early; the flush excludes them.
982 uint32_t excluded = 0U;
983 if (all_to_all && word * warp_width <= query_id_offset + query_index) {
984 auto const skipped = query_id_offset + query_index - word * warp_width + 1U;
985 excluded = skipped >= warp_width ? ~0U : (1U << skipped) - 1U;
986 }
987 passed = sum_posting_bitmaps(
988 bitmaps,
989 starts,
990 count,
991 word,
992 minimum_matches,
993 minimum_matches == 0U ? ~0U : excluded,
994 counters
995 );
996 }
997 auto const shift = (lane % 16U) * 2U;
998 for (uint32_t pair = 0U; pair < warp_width / 2U; ++pair) {
999 auto const source = 2U * pair + lane / 16U;
1000 uint32_t packed = 0U;
1001 _Pragma("unroll")
1002 for (uint32_t plane = 0U; plane < CounterBits; ++plane) {
1003 auto const bits =
1004 (__shfl_sync(0xffffffffU, counters[plane], source) >> shift) & 3U;
1005 packed |= ((bits & 1U) | ((bits & 2U) << 15U)) << plane;
1006 }
1007 auto const pass = __shfl_sync(0xffffffffU, passed, source) >> shift;
1008 if (pass & 1U) {
1009 packed = (packed & 0xffff0000U) | minimum_matches;
1010 }
1011 if (pass & 2U) {
1012 packed = (packed & 0xffffU) | (minimum_matches << 16U);
1013 }
1014 auto const slot = (base + source) * 16U + lane % 16U;
1015 if (slot < (width + 1U) / 2U) {
1016 packed_counts[slot] = packed;
1017 }
1018 }
1019 }
1020 } else {
__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 bool uint32_t uint32_t const uint32_t const * bitmap_starts
Definition kernels.cuh:931
auto const shift
Definition kernels.cuh:442
__shared__ uint32_t packed_counts[]
auto const lane
Definition kernels.cuh:966
__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 bool uint32_t uint32_t const uint32_t const uint32_t const * bitmap_counts
Definition kernels.cuh:933
auto const width
Definition kernels.cuh:963
Here is the call graph for this function:

◆ consume_byte()

void cuddl::detail::consume_byte ( char  ch,
uint32_t  k,
uint64_t  mask,
span_result &  result,
uint64_t &  window,
uint32_t &  window_len 
)
inline

Feeds one byte of sequence through the DDL rolling-window extraction.

Definition at line 236 of file fasta_parser.hpp.

243 {
245 return;
246 }
247 auto const encoded = encode_base(ch);
248 if (encoded == 0xffu) {
249 if (window_len < k && window_len > 0) {
250 ++result.invalid_windows;
251 }
252 window_len = 0;
253 window = 0;
254 return;
255 }
256 ++result.bases;
257 window = ((window << 2U) | encoded) & mask;
258 if (window_len < k) {
259 ++window_len;
260 }
261 if (window_len == k) {
262 auto const rev = reverse_complement(window, k);
263 result.kmers.push_back(window > rev ? window : rev);
264 ++result.valid_kmers;
265 }
266}
constexpr uint64_t reverse_complement(uint64_t packed, uint32_t k) noexcept
Reverse-complements a packed k-mer (2 bits per base) with bit-parallel reversal.
std::vector< uint64_t > kmers
Here is the call graph for this function:

◆ crc32_combine()

uint32_t cuddl::detail::crc32_combine ( uint32_t  a,
uint32_t  b,
uint64_t  length 
)
inlinenoexcept

CRC32 of A followed by B, from CRC32(A), CRC32(B) and B's length, as zlib's crc32_combine: CRC32(A) times x^(8 * length) modulo the reflected polynomial, plus CRC32(B).

Definition at line 87 of file device_gzip.cuh.

87 {
88 constexpr uint32_t polynomial = 0xedb88320U;
89 auto const multiply = [](uint32_t x, uint32_t y) {
90 uint32_t product = 0;
91 for (uint32_t bit = 1U << 31U; bit != 0; bit >>= 1U) {
92 if ((x & bit) != 0) product ^= y;
93 y = (y & 1U) != 0 ? (y >> 1U) ^ polynomial : y >> 1U;
94 }
95 return product;
96 };
97 // power = x^(2^k) for bit k of 8 * length; x^1 is 1 << 30 in reflected order.
98 uint32_t shift = 1U << 31U, power = 1U << 30U;
99 for (uint64_t bits = length * 8; bits != 0; bits >>= 1U) {
100 if ((bits & 1U) != 0) shift = multiply(power, shift);
101 power = multiply(power, power);
102 }
103 return multiply(shift, a) ^ b;
104}
Here is the caller graph for this function:

◆ database_file_little_endian()

template<typename T >
T cuddl::detail::database_file_little_endian ( T  value)
inline

Definition at line 40 of file reference_database_file.cuh.

40 {
41 if constexpr (std::endian::native == std::endian::little) {
42 return value;
43 } else {
44 T result = 0;
45 for (size_t i = 0; i < sizeof(T); ++i) {
46 result = static_cast<T>((result << 8U) | (value & 0xffU));
47 value >>= 8U;
48 }
49 return result;
50 }
51}
Here is the caller graph for this function:

◆ database_file_metadata()

template<typename IO >
Result< void > cuddl::detail::database_file_metadata ( IO &  io,
reference_database_metadata &  metadata,
uint32_t &  blacklist_count 
)

Definition at line 135 of file reference_database_file.cuh.

135 {
136 auto& c = metadata.compatibility;
137 CUDDL_TRY(io.value(c.kmer_length));
138 CUDDL_TRY(io.value(c.exponent_bits));
139 CUDDL_TRY(io.value(c.bucket_count));
140 CUDDL_TRY(io.value(c.key_mask));
141 CUDDL_TRY(io.value(metadata.reference_count));
142 CUDDL_TRY(io.value(blacklist_count));
143 CUDDL_TRY(io.value(c.blacklist_identity));
144 return Ok();
145}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_fastx_sequence_file()

Result< std::unique_ptr< fastx_sequence_file > > cuddl::detail::decode_fastx_sequence_file ( std::unique_ptr< fastx_mapped_file >  file,
std::string const &  path,
decompression_source  source,
bool  defer_fasta,
uint32_t  pack_k = 0 
)
inline

Definition at line 1670 of file fastx_sequence_file.hpp.

1676 {
1677 auto result = std::make_unique<fastx_sequence_file>();
1678 result->file = std::move(file);
1679 auto data = result->file->data();
1680 auto& decompressed = result->decompressed;
1681 if (data.size() >= 2 && static_cast<unsigned char>(data[0]) == 0x1f &&
1682 static_cast<unsigned char>(data[1]) == 0x8b) {
1683 if (pack_k >= 16 && !defer_fasta && source.acquire != nullptr) {
1684 auto const fastq = CUDDL_TRY(inflate_and_pack(*result, data, path, source, pack_k));
1685 if (!fastq) return result;
1686 return finish_fastx_sequence_file(std::move(result), *fastq, path, false, pack_k);
1687 }
1688 // A single-member file's ISIZE is its inflated length. Asking for that rather than an
1689 // over-estimate keeps loader buffers small, which matters most when they are
1690 // page-locked, since the driver pins one allocation at a time. ISIZE sizes
1691 // nothing for a BGZF stream, which ends in an empty EOF block that reads back as zero, so
1692 // a hint below the compressed size falls back to the over-estimate the heap path assumes.
1693 // A concatenated stream advertises only its last member; a short target grows the
1694 // request, which keeps a multi-member file off the heap path.
1695 auto const hint = gzip_size_hint(data);
1696 auto request = hint >= data.size() ? hint : data.size() * 8;
1697 auto target = source.request(request);
1698 bool insufficient = false;
1699 bool inflated = false;
1700 size_t produced = 0;
1701 while (target.data != nullptr && target.capacity > 0) {
1702 auto const written = gunzip_members_direct(data, target, insufficient, path);
1703 if (!written) return Err(written.error());
1704 produced = *written;
1705 if (!insufficient) {
1706 inflated = true;
1707 break;
1708 }
1709 request = std::max(request * 2, data.size());
1710 target = source.request(request);
1711 }
1712 if (inflated) {
1713 result->decompressed_target = target.data;
1714 result->decompressed_size = produced;
1715 result->storage_owner = std::move(target.owner);
1716 data = std::string_view{target.data, produced};
1717 }
1718 if (result->decompressed_target == nullptr) {
1719 auto inflated = gunzip_members_into(data, decompressed, path);
1720 if (!inflated) return Err(inflated.error());
1721 data = decompressed;
1722 }
1723 } else if (source.acquire != nullptr && !defer_fasta) {
1724 // Plain bytes go to the caller's buffer, as inflated bytes do: the parser compacts them
1725 // in place and the stager reads them without another copy.
1726 auto target = source.request(data.size());
1727 if (target.data != nullptr) {
1728 std::memcpy(target.data, data.data(), data.size());
1729 result->decompressed_target = target.data;
1730 result->decompressed_size = data.size();
1731 result->storage_owner = std::move(target.owner);
1732 data = std::string_view{target.data, data.size()};
1733 }
1734 }
1735 return finish_fastx_sequence_file(std::move(result), data, path, defer_fasta, pack_k);
1736}
Result< std::unique_ptr< fastx_sequence_file > > finish_fastx_sequence_file(std::unique_ptr< fastx_sequence_file > result, std::string_view data, std::string const &path, bool defer_fasta, uint32_t pack_k)
Parses, packs or defers the bytes data of a loaded file, which result holds.
Result< void > gunzip_members_into(std::string_view input, std::string &output, std::string const &path)
Decompresses gzip members from a mapped buffer into output, appending.
Result< std::optional< std::string_view > > inflate_and_pack(fastx_sequence_file &result, std::string_view input, std::string const &path, decompression_source source, uint32_t k)
Inflates gzip input in this thread's reusable heap buffer and, for FASTA, packs it there and moves on...
decompression_target request(size_t bytes) const
decompression_target(* acquire)(void *context, size_t bytes)
Here is the call graph for this function:

◆ decompress_gzip_member()

libdeflate_result cuddl::detail::decompress_gzip_member ( libdeflate_decompressor *  decoder,
std::string_view  input,
char *  output,
size_t  capacity,
size_t &  consumed,
size_t &  produced 
)
inline

Decodes one gzip member, checking structure and ISIZE but not checksums.

Skipping the payload CRC avoids a second pass over every decompressed byte.

Definition at line 836 of file fastx_sequence_file.hpp.

843 {
844 auto const byte = [&](size_t i) {
845 return static_cast<uint32_t>(static_cast<unsigned char>(input[i]));
846 };
847 if (input.size() < 18 || byte(0) != 0x1f || byte(1) != 0x8b || byte(2) != 8 ||
848 (byte(3) & 0xe0) != 0) {
849 return LIBDEFLATE_BAD_DATA;
850 }
851 auto const flags = byte(3);
852 auto const end = input.size() - 8;
853 size_t at = 10;
854 if ((flags & 4) != 0) {
855 if (end - at < 2) return LIBDEFLATE_BAD_DATA;
856 auto const length = byte(at) | (byte(at + 1) << 8);
857 at += 2;
858 if (length > end - at) return LIBDEFLATE_BAD_DATA;
859 at += length;
860 }
861 for (auto const flag : {8U, 16U}) {
862 if ((flags & flag) != 0) {
863 auto const terminator = input.find('\0', at);
864 if (terminator == std::string_view::npos || terminator >= end) {
865 return LIBDEFLATE_BAD_DATA;
866 }
867 at = terminator + 1;
868 }
869 }
870 if ((flags & 2) != 0) {
871 if (end - at < 2) return LIBDEFLATE_BAD_DATA;
872 at += 2;
873 }
874 size_t deflate_bytes = 0;
875 auto const status = libdeflate_deflate_decompress_ex(
876 decoder, input.data() + at, end - at, output, capacity, &deflate_bytes, &produced
877 );
878 if (status != LIBDEFLATE_SUCCESS) return status;
879 auto const trailer = at + deflate_bytes;
880 auto const size = byte(trailer + 4) | (byte(trailer + 5) << 8) | (byte(trailer + 6) << 16) |
881 (byte(trailer + 7) << 24);
882 if (static_cast<uint32_t>(produced) != size) return LIBDEFLATE_BAD_DATA;
883 consumed = trailer + 8;
884 return LIBDEFLATE_SUCCESS;
885}
Here is the caller graph for this function:

◆ device_pinned_pool()

cuda::pinned_memory_pool_ref cuddl::detail::device_pinned_pool ( cuda::device_ref  device,
pinned_placement  placement 
)
inline

A page-locked memory pool accessible from device only.

CCCL's default pinned pool is accessible from every visible device, so every allocation is mapped into all of them. With one MPI rank per GPU and all 8 GPUs of an A40 node visible to every rank, a 4-8 MB allocation took 27 ms instead of 1.3 ms, and sketch loaders spent most of their time waiting on the driver. Downloads into it with 8 ranks took 1.3 s, against 0.39 s from a device-node pool.

One pool per device and placement for the life of the process, like CUDA's default pools: never destroying it keeps it valid for buffers that live until process exit.

Definition at line 40 of file pinned_pool.cuh.

40 {
41 static std::mutex mutex;
42 static std::vector<std::array<cudaMemPool_t, 2>> pools(cuda::devices.size());
43 std::lock_guard lock(mutex);
44 auto& pool = pools.at(static_cast<size_t>(device.get()))[static_cast<size_t>(placement)];
45 if (pool == nullptr) {
46 // Ablated, the pool behaves like CCCL's default pinned pool: mapped into every visible
47 // device and returning freed pages to the OS at every synchronisation. numa-pool
48 // separately puts device_node pages on any node.
49 bool const ablate = ablated("pinned-pool");
50 int node = -1;
51 CUDDL_CUDA_CALL(cudaDeviceGetAttribute(&node, cudaDevAttrHostNumaId, device.get()));
52 bool const numa =
53 !ablated("numa-pool") && placement == pinned_placement::device_node && node >= 0 &&
54 device.attribute(cuda::device_attributes::host_numa_memory_pools_supported) != 0;
55 cudaMemPoolProps properties{};
56 properties.allocType = cudaMemAllocationTypePinned;
57 properties.location = numa ? cudaMemLocation{cudaMemLocationTypeHostNuma, node}
58 : cudaMemLocation{cudaMemLocationTypeHost, 0};
59 cudaMemPool_t created = nullptr;
60 CUDDL_CUDA_CALL(cudaMemPoolCreate(&created, &properties));
61 cudaMemAccessDesc const access{
62 {cudaMemLocationTypeDevice, device.get()}, cudaMemAccessFlagsProtReadWrite
63 };
64 CUDDL_CUDA_CALL(cudaMemPoolSetAccess(created, &access, 1));
65 if (ablate) {
66 for (auto const& other : cuda::devices) {
67 cudaMemAccessDesc const shared{
68 {cudaMemLocationTypeDevice, other.get()}, cudaMemAccessFlagsProtReadWrite
69 };
70 CUDDL_CUDA_CALL(cudaMemPoolSetAccess(created, &shared, 1));
71 }
72 }
73 // Pinning is the expensive part of an allocation: with the default threshold of zero,
74 // every synchronisation returns freed pages to the OS and the next allocation pins them
75 // again, which cost sketch loaders more time than inflating their files.
76 auto keep = ablate ? uint64_t{0} : std::numeric_limits<uint64_t>::max();
77 CUDDL_CUDA_CALL(cudaMemPoolSetAttribute(created, cudaMemPoolAttrReleaseThreshold, &keep));
78 pool = created;
79 }
80 return cuda::pinned_memory_pool_ref(pool);
81}
#define CUDDL_CUDA_CALL(err)
Checks a CUDA runtime call and throws std::runtime_error on failure.
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:

◆ device_reads_pageable_memory()

bool cuddl::detail::device_reads_pageable_memory ( cuda::device_ref  device)
inlinenoexcept

Whether device reads pageable host memory itself.

This is what makes a CPU/GPU system coherent, Grace Hopper and Grace Blackwell among them, and what lets a kernel read the loader's buffer instead of a copy of it.

Definition at line 80 of file database_staging.cuh.

80 {
81 return device.attribute(cuda::device_attributes::pageable_memory_access) != 0 &&
82 device.attribute(
83 cuda::device_attributes::pageable_memory_access_uses_host_page_tables
84 ) != 0;
85}
Here is the caller graph for this function:

◆ directory_posting_range()

__device__ uint2 cuddl::detail::directory_posting_range ( uint16_t const *  sorted_keys,
uint32_t const *  directory,
uint32_t  reference_count,
uint32_t  bucket,
uint32_t  sparse_key 
)
inline

One sparse bucket/key posting range, narrowed through the bucket's key directory.

Definition at line 505 of file kernels.cuh.

511 {
512 auto const* first = sorted_keys + static_cast<size_t>(bucket) * reference_count;
513 auto const* cells =
514 directory + static_cast<size_t>(bucket) * sparse_directory_entries(reference_count);
515 auto const cell = sparse_key >> sparse_directory_shift(reference_count);
516 auto const* low = first + cells[cell];
517 auto const* high = first + cells[cell + 1U];
518 auto const less = [](uint16_t key, uint32_t value) {
519 return key < value;
520 };
521 auto const* begin = cuda::std::lower_bound(low, high, sparse_key, less);
522 auto const* end = cuda::std::lower_bound(begin, high, sparse_key + 1U, less);
523 return {static_cast<uint32_t>(begin - sorted_keys), static_cast<uint32_t>(end - sorted_keys)};
524}
__global__ uint16_t const * sorted_keys
Definition kernels.cuh:469
__global__ uint32_t uint32_t uint32_t * directory
Definition kernels.cuh:440
__host__ __device__ constexpr uint32_t sparse_directory_shift(uint32_t reference_count) noexcept
Low key bits a sparse key directory cell spans for reference_count references.
Definition kernels.cuh:420
__host__ __device__ constexpr uint32_t sparse_directory_entries(uint32_t reference_count) noexcept
Bounds per bucket in a sparse key directory.
Definition kernels.cuh:429
Here is the call graph for this function:

◆ dlc()

__device__ double cuddl::detail::dlc ( uint32_t const *  bins,
double  bucket_count,
double  lc 
)
inlinenoexcept

Distributed linear-counting estimate, blended from per-tier estimates by precision.

Definition at line 98 of file detail/hybrid_cardinality.cuh.

98 {
99 auto const empty = bins[0];
100 auto const minimum = max(1.0, bucket_count * dlc_min_fraction);
101 auto const maximum = bucket_count - minimum;
102 auto cumulative = empty;
103 double weight_sum = 0.0;
104 double weighted_log_sum = 0.0;
105 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
106 if (cumulative >= minimum && cumulative <= maximum) {
107 auto const occupied = bucket_count - cumulative;
108 auto const log_ratio = cuda::std::log(bucket_count / cumulative);
109 auto const tier_estimate = cuda::std::ldexp(bucket_count * log_ratio, tier);
110
111 auto const error = cuda::std::sqrt(2.0 / 3.14159265358979323846) *
112 cuda::std::sqrt(occupied / (bucket_count * cumulative)) / log_ratio;
113
114 // `1 / error` is positive here. `pow(x, 4.5)` is the same value as
115 // `x^4 * sqrt(x)` and avoids the generic double-precision pow path.
116 auto const inv_error = 1.0 / error;
117 auto const inv_squared = inv_error * inv_error;
118 auto const weight = inv_squared * inv_squared * cuda::std::sqrt(inv_error);
119 weight_sum += weight;
120 weighted_log_sum += weight * cuda::std::log(tier_estimate);
121 }
122 cumulative += bins[tier + 1];
123 if (cumulative >= bucket_count) {
124 break;
125 }
126 }
127 if (weight_sum == 0.0) {
128 return lc;
129 }
130 auto const pure = cuda::std::exp(weighted_log_sum / weight_sum);
131 if (empty >= dlc_blend_high * bucket_count) {
132 return lc;
133 }
134 if (empty > dlc_blend_low * bucket_count) {
135 auto const t = (empty - dlc_blend_low * bucket_count) /
136 ((dlc_blend_high - dlc_blend_low) * bucket_count);
137 return t * lc + (1.0 - t) * pure;
138 }
139 return pure;
140}
constexpr double dlc_blend_low
constexpr int32_t nlz_bins
NLZ histogram bins for the hybrid estimator, including the empty tier.
Here is the caller graph for this function:

◆ dlc_f32()

__device__ float cuddl::detail::dlc_f32 ( uint32_t const *  bins,
float  bucket_count,
float  lc 
)
inlinenoexcept

Single-precision dlc for the latency-bound hybrid scan.

Definition at line 239 of file detail/hybrid_cardinality.cuh.

239 {
240 auto const empty = static_cast<float>(bins[0]);
241 auto const minimum = max(1.0f, bucket_count * static_cast<float>(dlc_min_fraction));
242 auto const maximum = bucket_count - minimum;
243 auto cumulative = empty;
244 float weight_sum = 0.0f;
245 float weighted_log_sum = 0.0f;
246 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
247 if (cumulative >= minimum && cumulative <= maximum) {
248 auto const occupied = bucket_count - cumulative;
249 auto const log_ratio = cuda::std::log(bucket_count / cumulative);
250 auto const tier_estimate = cuda::std::ldexp(bucket_count * log_ratio, tier);
251
252 auto const error = cuda::std::sqrt(2.0f / 3.14159265358979323846f) *
253 cuda::std::sqrt(occupied / (bucket_count * cumulative)) / log_ratio;
254
255 // `1 / error` is positive here. `pow(x, 4.5)` is the same value as
256 // `x^4 * sqrt(x)` and avoids a generic pow path.
257 auto const inv_error = 1.0f / error;
258 auto const inv_squared = inv_error * inv_error;
259 auto const weight = inv_squared * inv_squared * cuda::std::sqrt(inv_error);
260 weight_sum += weight;
261 weighted_log_sum += weight * cuda::std::log(tier_estimate);
262 }
263 cumulative += bins[tier + 1];
264 if (cumulative >= bucket_count) {
265 break;
266 }
267 }
268 if (weight_sum == 0.0f) {
269 return lc;
270 }
271 auto const pure = cuda::std::exp(weighted_log_sum / weight_sum);
272 if (empty >= static_cast<float>(dlc_blend_high) * bucket_count) {
273 return lc;
274 }
275 if (empty > static_cast<float>(dlc_blend_low) * bucket_count) {
276 auto const t = (empty - static_cast<float>(dlc_blend_low) * bucket_count) /
277 ((static_cast<float>(dlc_blend_high) - static_cast<float>(dlc_blend_low)) *
278 bucket_count);
279 return t * lc + (1.0f - t) * pure;
280 }
281 return pure;
282}
constexpr double dlc_blend_high
Here is the caller graph for this function:

◆ download_store()

template<size_t BucketCount>
Result< void > cuddl::detail::download_store ( device_span< uint32_t const >  store,
std::span< uint16_t >  scores,
cuda::stream_ref  stream 
)

Copies the winner scores of device store rows into host score rows.

Scores are extracted on the device, so only two bytes per bucket cross the bus. scores must stay alive until stream reaches this point.

Definition at line 844 of file database_staging.cuh.

848 {
849 auto const genomes = scores.size() / BucketCount;
850 if (genomes == 0) return Ok();
851 auto device_scores =
852 cuda::make_device_buffer<uint16_t>(stream, stream.device(), scores.size(), cuda::no_init);
853 batch_scores_kernel<BucketCount>
854 <<<static_cast<uint32_t>(genomes), block_size, 0, stream.get()>>>(
855 store.data(), static_cast<uint32_t>(genomes), device_scores.data()
856 );
857 CUDDL_CUDA_TRY(cudaGetLastError());
858 CUDDL_CUDA_TRY(cuda::copy_bytes(stream, device_scores, scores));
859 return Ok();
860}
Here is the call graph for this function:

◆ encode_base()

constexpr uint8_t cuddl::detail::encode_base ( char  base)
inlineconstexprnoexcept

Maps one DNA byte to its 2-bit symbol (A=0, C=1, T=2, G=3) or 0xff when invalid.

Case-insensitive; any ambiguous byte (including 'N') is invalid. The __host__ __device__ annotation only exists under the CUDA compiler so plain host translation units (e.g. via cuddl/fastx.hpp) see a normal inline function.

Definition at line 16 of file dna.hpp.

16 {
17 auto const byte = static_cast<uint8_t>(base);
18 auto const upper = static_cast<uint8_t>(byte & 0xDFu);
19 auto const x = (byte >> 1u) & 3u;
20 auto const valid =
21 static_cast<uint8_t>((upper == 'A') | (upper == 'C') | (upper == 'G') | (upper == 'T'));
22 auto const mask = static_cast<uint8_t>(0u - valid);
23 return static_cast<uint8_t>((x & mask) | (0xFFu & ~mask));
24}
Here is the caller graph for this function:

◆ external_batch_query_limit()

uint32_t cuddl::detail::external_batch_query_limit ( uint32_t  reference_count,
cuda::device_ref  device 
)
inline

Query rows one external batch tile may hold on device.

A tile's results, match counts and counters cost about 20 bytes per pair, and a quarter of the device's memory goes to them, so a 16 GB device holds about 200 million pairs per tile. Larger tiles give the counting kernels more queries to overlap. The limit depends only on the device, so storage sized from the requirements always matches the search.

Definition at line 996 of file reference_database.cuh.

996 {
997 constexpr size_t bytes_per_pair = sizeof(packed_pairwise_counts) + 3U * sizeof(uint32_t);
998 auto const budget = device.attribute(cuda::device_attributes::total_global_memory) / 4U;
999 auto const pairs = budget / bytes_per_pair;
1000 auto const queries = reference_count == 0U ? pairs : pairs / reference_count;
1001 return static_cast<uint32_t>(
1002 std::clamp<size_t>(queries, batch_query_tile_count, std::numeric_limits<uint32_t>::max())
1003 );
1004}
auto const pairs
Definition kernels.cuh:1592
One pair's exact bucket comparison counts in 64 bits, the storage and transfer form of batch-search r...

◆ fasta_next_header()

char const * cuddl::detail::fasta_next_header ( char const *  first,
char const *  last 
)
inline

First '>' in [first, last) that starts a line, or last.

The byte before first must be a line ending.

Definition at line 1229 of file fastx_sequence_file.hpp.

1229 {
1230 for (auto const* at = first; at != last; ++at) {
1231 at = static_cast<char const*>(std::memchr(at, '>', static_cast<size_t>(last - at)));
1232 if (at == nullptr) return last;
1233 if (at == first || at[-1] == '\n' || at[-1] == '\r') return at;
1234 }
1235 return last;
1236}
Here is the caller graph for this function:

◆ fasta_sequence_compact()

char * cuddl::detail::fasta_sequence_compact ( char const *  first,
char const *  last,
char *  out,
char const *&  stop 
)
inline

Compacts one FASTA record's sequence from first to out, stopping at the next header.

The byte before first must be a line ending, and out may alias first. stop receives the next line-start '>' or last.

Returns
The end of the written span.

Definition at line 1366 of file fastx_sequence_file.hpp.

1366 {
1367#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
1368 if (compact_avx512) return fasta_sequence_compact_avx512(first, last, out, stop);
1369 if (compact_avx2) return fasta_sequence_compact_avx2(first, last, out, stop);
1370#endif
1371#if defined(__ARM_NEON)
1372 if (simd_compaction) return fasta_sequence_compact_neon(first, last, out, stop);
1373#endif
1374 stop = fasta_next_header(first, last);
1375 return compact_sequence_whitespace(first, stop, out);
1376}
char const * fasta_next_header(char const *first, char const *last)
First '>' in [first, last) that starts a line, or last.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ fastx_fasta_compact_extents()

std::vector< fastx_sequence_extent > cuddl::detail::fastx_fasta_compact_extents ( std::string_view  data,
char *  out 
)
inline

Collects FASTA sequence extents with whitespace compacted out, in one pass over data.

An extent spans the bases between one line-start '>' header line and the next header (or the end of data); bytes before the first header belong to no extent. Bases of consecutive records are packed back to back from out, which may alias data.

Definition at line 1384 of file fastx_sequence_file.hpp.

1384 {
1385 std::vector<fastx_sequence_extent> extents;
1386 size_t header = data.find('>');
1387 while (header != std::string_view::npos && header != 0 && data[header - 1] != '\n' &&
1388 data[header - 1] != '\r') {
1389 header = data.find('>', header + 1);
1390 }
1391 if (header == std::string_view::npos) return extents;
1392 auto const* const last = data.data() + data.size();
1393 while (true) {
1394 auto const end = fastx_line_end(data, header);
1395 if (end + 1 >= data.size()) break;
1396 auto const* const sequence = data.data() + end + 1;
1397 char const* stop = nullptr;
1398 auto* const begin = out;
1399 out = fasta_sequence_compact(sequence, last, out, stop);
1400 if (stop != sequence) extents.push_back({begin, out});
1401 if (stop == last) break;
1402 header = static_cast<size_t>(stop - data.data());
1403 }
1404 return extents;
1405}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ fastx_is_sequence_whitespace()

constexpr bool cuddl::detail::fastx_is_sequence_whitespace ( char  ch)
inlineconstexprnoexcept

True for bytes that FASTA sequence consumers conventionally skip between bases.

Definition at line 61 of file fastx_sequence_file.hpp.

61 {
62 return ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t';
63}
Here is the caller graph for this function:

◆ fastx_line_end()

size_t cuddl::detail::fastx_line_end ( std::string_view  data,
size_t  position 
)
inline

First line-end position at or after position using the resolved scanner.

Definition at line 195 of file fastx_sequence_file.hpp.

195 {
196 return line_end(data, position);
197}
Here is the caller graph for this function:

◆ fastx_line_end_scalar()

size_t cuddl::detail::fastx_line_end_scalar ( std::string_view  data,
size_t  position 
)
inline

Definition at line 92 of file fastx_sequence_file.hpp.

92 {
93 while (position < data.size() && data[position] != '\n' && data[position] != '\r') {
94 ++position;
95 }
96 return position;
97}
Here is the caller graph for this function:

◆ fastx_split_sequence_spans()

std::vector< fastx_sequence_span > cuddl::detail::fastx_split_sequence_spans ( std::vector< fastx_sequence_extent > const &  extents,
uint32_t  seed_bases,
uint32_t  count 
)
inline

Splits the combined FASTA sequence stream into count contiguous spans.

Each span carries the raw byte ranges of its slice of the sequence stream plus a materialised prefix of the preceding seed_bases valid bases (whitespace included, headers excluded, cut short at an invalid base or the buffer start). A rolling-window consumer that replays the prefix, then consumes the segments, reproduces the single-threaded byte stream exactly, so the per-span results can be concatenated in order. Whitespace between bases is preserved in both the prefix and the segments; consumers skip it with fastx_is_sequence_whitespace.

Definition at line 122 of file fasta_parser.hpp.

126 {
127 std::vector<fastx_sequence_span> spans;
128 if (extents.empty() || count == 0U) {
129 return spans;
130 }
131 size_t total = 0;
132 for (auto const& extent : extents) {
133 total += static_cast<size_t>(extent.end - extent.begin);
134 }
135 if (total == 0) {
136 return spans;
137 }
138 if (static_cast<size_t>(count) > total) {
139 count = static_cast<uint32_t>(total);
140 }
141 auto const per = total / count + (total % count != 0U);
142 spans.reserve(count);
143
144 for (uint32_t t = 0; t < count; ++t) {
145 auto const span_begin = static_cast<size_t>(t) * per;
146 if (span_begin >= total) {
147 break;
148 }
149 auto const span_end = span_begin + per < total ? span_begin + per : total;
150
151 fastx_sequence_span span;
152 span.total_bytes = span_end - span_begin;
153
154 // Locate the extent and offset of the span start.
155 size_t extent = 0;
156 size_t offset = span_begin;
157 while (extent < extents.size() &&
158 offset >= static_cast<size_t>(extents[extent].end - extents[extent].begin)) {
159 offset -= static_cast<size_t>(extents[extent].end - extents[extent].begin);
160 ++extent;
161 }
162
163 // Seed: walk back up to seed_bases valid bases (or until an invalid base / EOF).
164 size_t seed_extent = extent;
165 char const* seed_start = extents[extent].begin + offset;
166 {
167 size_t e = extent;
168 char const* q = seed_start;
169 uint32_t collected = 0;
170 while (collected < seed_bases && span_step_back(extents, e, q)) {
171 auto const ch = *q;
173 continue;
174 }
175 if (encode_base(ch) == 0xffu) {
176 seed_extent = e;
177 seed_start = q;
178 span_step_forward(extents, seed_extent, seed_start);
179 break;
180 }
181 ++collected;
182 seed_extent = e;
183 seed_start = q;
184 }
185 }
186
187 // Materialise the prefix from the seed start to the span start.
188 {
189 size_t e = seed_extent;
190 char const* q = seed_start;
191 char const* target = extents[extent].begin + offset;
192 while (e < extent || q < target) {
193 if (q >= extents[e].end) {
194 ++e;
195 q = extents[e].begin;
196 continue;
197 }
198 span.prefix.push_back(*q);
199 ++q;
200 }
201 }
202
203 // Collect the span's segments.
204 {
205 size_t remaining = span_end - span_begin;
206 char const* q = extents[extent].begin + offset;
207 size_t e = extent;
208 while (remaining > 0 && e < extents.size()) {
209 auto const limit = static_cast<size_t>(extents[e].end - q);
210 auto const step = limit < remaining ? limit : remaining;
211 span.segments.emplace_back(q, step);
212 remaining -= step;
213 if (remaining > 0) {
214 ++e;
215 if (e < extents.size()) {
216 q = extents[e].begin;
217 }
218 }
219 }
220 }
221
222 spans.push_back(std::move(span));
223 }
224 return spans;
225}
constexpr uint8_t encode_base(char base) noexcept
Maps one DNA byte to its 2-bit symbol (A=0, C=1, T=2, G=3) or 0xff when invalid.
Definition dna.hpp:16
Here is the call graph for this function:

◆ finalize()

constexpr __host__ __device__ __forceinline__ uint64_t cuddl::detail::finalize ( uint64_t  h)
constexpr

Applies the xxHash-64 final mixing (avalanche) step.

Definition at line 53 of file hash.cuh.

53 {
54 h ^= h >> 33;
55 h *= PRIME64_2;
56 h ^= h >> 29;
57 h *= PRIME64_3;
58 h ^= h >> 32;
59 return h;
60}
constexpr uint64_t PRIME64_3
Definition hash.cuh:35
constexpr uint64_t PRIME64_2
Definition hash.cuh:34
Here is the caller graph for this function:

◆ finish_fastx_sequence_file()

Result< std::unique_ptr< fastx_sequence_file > > cuddl::detail::finish_fastx_sequence_file ( std::unique_ptr< fastx_sequence_file >  result,
std::string_view  data,
std::string const &  path,
bool  defer_fasta,
uint32_t  pack_k 
)
inline

Parses, packs or defers the bytes data of a loaded file, which result holds.

Definition at line 1575 of file fastx_sequence_file.hpp.

1581 {
1582 result->input = data;
1583 if (defer_fasta) {
1584 auto const first = data.find_first_not_of("\r\n");
1585 if (first != std::string_view::npos && data[first] == '>') {
1586 auto const end = fastx_line_end(data, first);
1587 // Empty first records and malformed inputs stay with the CPU parser.
1588 if (end + 1 < data.size() && data[end + 1] != '>') return result;
1589 }
1590 }
1591 if (pack_k >= 16) {
1592 auto const first = data.find_first_not_of("\r\n");
1593 if (first == std::string_view::npos || data[first] != '@') {
1594 char* out = result->decompressed_target;
1595 if (out == nullptr) {
1596 if (data.data() != result->decompressed.data())
1597 result->decompressed.resize(data.size());
1598 out = result->decompressed.data();
1599 }
1600 CUDDL_TRY(pack_fasta_chunks(*result, data, out, pack_k, path));
1601 return result;
1602 }
1603 }
1604 CUDDL_TRY(parse_fastx_sequence_file(*result, path));
1605 return result;
1606}
Result< void > pack_fasta_chunks(fastx_sequence_file &result, std::string_view data, char *out, uint32_t k, std::string const &path)
Compact and pack one cache-sized block at a time, without a genome-sized ASCII write.
size_t fastx_line_end(std::string_view data, size_t position)
First line-end position at or after position using the resolved scanner.
Result< void > parse_fastx_sequence_file(fastx_sequence_file &result, std::string const &path)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ for()

cuddl::detail::for ( )

Definition at line 444 of file kernels.cuh.

445 {
446 auto const bucket = i / entries;
447 auto const bound = static_cast<uint32_t>(i % entries) << shift;
448 auto const* first = sorted_keys + bucket * reference_count;
449 directory[i] = static_cast<uint32_t>(
450 cuda::std::lower_bound(
451 first,
452 first + reference_count,
453 bound,
454 [](uint16_t key, uint32_t value) { return key < value; }
455 ) -
456 first
457 );
458 }

◆ gunzip_members_direct()

Result< size_t > cuddl::detail::gunzip_members_direct ( std::string_view  input,
decompression_target  target,
bool &  insufficient,
std::string const &  path 
)
inline

Decompresses gzip members straight into a caller's buffer.

Returns Error::resource when target is too small, which callers treat as a request to fall back to the growing-string path rather than a failure.

Definition at line 931 of file fastx_sequence_file.hpp.

936 {
937 insufficient = false;
938 static thread_local shared_gzip_decompressor shared;
939 if (shared.handle == nullptr) {
940 return Err(Error::resource("cannot allocate gzip decompressor"));
941 }
942 size_t consumed = 0;
943 size_t produced = 0;
944 while (consumed < input.size()) {
945 size_t member_in = 0;
946 size_t member_out = 0;
947 auto const status = decompress_gzip_member(
948 shared.handle,
949 input.substr(consumed),
950 target.data + produced,
951 target.capacity - produced,
952 member_in,
953 member_out
954 );
955 if (status == LIBDEFLATE_INSUFFICIENT_SPACE) {
956 // A caller-supplied buffer cannot grow, so the loader falls back instead.
957 insufficient = true;
958 return Result<size_t>::ok(0);
959 }
960 if (status != LIBDEFLATE_SUCCESS) {
961 return Err(Error::invalid_argument("gzip FASTX error: invalid stream: " + path));
962 }
963 consumed += member_in;
964 produced += member_out;
965 }
966 return produced;
967}
Fallible API result: cuda::std::expected<T, Error> with cuDDL factories.
Definition error.hpp:118
libdeflate handle, one per thread because the object is single-threaded.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ gunzip_members_into()

Result< void > cuddl::detail::gunzip_members_into ( std::string_view  input,
std::string &  output,
std::string const &  path 
)
inline

Decompresses gzip members from a mapped buffer into output, appending.

Members are decompressed one at a time so concatenated streams (BGZF among them) work. Each member's structure, DEFLATE data and length trailer are checked, but CRC32 is not. output grows as needed, so pre-sizing storage saves reallocations without truncating data.

Definition at line 893 of file fastx_sequence_file.hpp.

893 {
894 static thread_local shared_gzip_decompressor shared;
895 if (shared.handle == nullptr) {
896 return Err(Error::resource("cannot allocate gzip decompressor"));
897 }
898 // Genomes compress around 3x; guessing low costs a realloc per member, not correctness.
899 if (output.empty()) output.resize(std::max<size_t>(input.size() * 4, 1U << 16));
900 size_t consumed = 0;
901 size_t produced = 0;
902 while (consumed < input.size()) {
903 size_t member_in = 0;
904 size_t member_out = 0;
905 auto const status = decompress_gzip_member(
906 shared.handle,
907 input.substr(consumed),
908 output.data() + produced,
909 output.size() - produced,
910 member_in,
911 member_out
912 );
913 if (status == LIBDEFLATE_INSUFFICIENT_SPACE) {
914 output.resize(output.size() * 2 + 1);
915 continue;
916 }
917 if (status != LIBDEFLATE_SUCCESS) {
918 return Err(Error::invalid_argument("gzip FASTX error: invalid stream: " + path));
919 }
920 consumed += member_in;
921 produced += member_out;
922 }
923 output.resize(produced);
924 return Ok();
925}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ gzip_decompressed_size()

uint64_t cuddl::detail::gzip_decompressed_size ( std::string const &  path)
inline

Uncompressed size the gzip trailer advertises, or 0 when it cannot be read.

ISIZE holds one member's length modulo 2^32, so a concatenated stream is only sized correctly by its last member. A short buffer is refused later, never silently truncated.

Exact decompressed size of path, or 0 when it is not a readable gzip file.

A gzip member ends with a little-endian ISIZE, so this needs a 4-byte read instead of a full decompression pass. A corpus of these bounds how much a staged arena can ever hold.

Definition at line 977 of file fastx_sequence_file.hpp.

977 {
978 auto file = std::fopen(path.c_str(), "rb");
979 if (file == nullptr) return 0;
980 uint64_t size = 0;
981 if (std::fseek(file, -4, SEEK_END) == 0) {
982 unsigned char trailer[4] = {};
983 if (std::fread(trailer, 1, sizeof(trailer), file) == sizeof(trailer)) {
984 size = static_cast<uint64_t>(trailer[0]) | (static_cast<uint64_t>(trailer[1]) << 8) |
985 (static_cast<uint64_t>(trailer[2]) << 16) |
986 (static_cast<uint64_t>(trailer[3]) << 24);
987 }
988 }
989 std::fclose(file);
990 return size;
991}
Here is the caller graph for this function:

◆ gzip_size_hint()

size_t cuddl::detail::gzip_size_hint ( std::string_view  input)
inline

Definition at line 993 of file fastx_sequence_file.hpp.

993 {
994 if (input.size() < 8) return 0;
995 auto const byte = [&](size_t index) {
996 return static_cast<uint32_t>(static_cast<unsigned char>(input[index]));
997 };
998 auto const tail = input.size() - 4;
999 return byte(tail) | (byte(tail + 1) << 8) | (byte(tail + 2) << 16) | (byte(tail + 3) << 24);
1000}
Here is the caller graph for this function:

◆ has_gzip_signature_portable()

bool cuddl::detail::has_gzip_signature_portable ( char const *  data,
size_t  size 
)
inline

True when data holds what could start another gzip member: 1f 8b 08, then a flag byte with its reserved bits clear.

A member with reserved flag bits set fails to decode, so it cannot be one the host path would inflate. Compressed data holds the three-byte signature alone in about one file in eleven, which would send those files back to the host.

Definition at line 221 of file device_gzip.cuh.

221 {
222 for (size_t at = 0; size - at >= 4;) {
223 auto const* const found =
224 static_cast<char const*>(::memmem(data + at, size - at - 1, "\x1f\x8b\x08", 3));
225 if (found == nullptr) return false;
226 if ((static_cast<uint8_t>(found[3]) & 0xe0U) == 0) return true;
227 at = static_cast<size_t>(found - data) + 1;
228 }
229 return false;
230}
Here is the caller graph for this function:

◆ hash_kmer()

__host__ __device__ constexpr uint64_t cuddl::detail::hash_kmer ( uint64_t  packed_kmer)
constexprnoexcept

Hashes a canonical packed k-mer into the value space used by DDL registers.

Definition at line 21 of file hash.cuh.

21 {
22 return splitmix64(packed_kmer ^ seed);
23}
__host__ __device__ constexpr uint64_t splitmix64(uint64_t value) noexcept
SplitMix64 finalizer, applied to a packed k-mer XOR the domain seed.
Definition hash.cuh:9
Here is the call graph for this function:

◆ hybrid()

__device__ double cuddl::detail::hybrid ( double  low_estimate,
double  zone_estimate,
double  corrected_mean,
double  bucket_count 
)
inlinenoexcept

Log-blended low and corrected estimates across the hybrid transition zone.

Definition at line 143 of file detail/hybrid_cardinality.cuh.

148 {
149 auto const low = hybrid_low * bucket_count;
150 auto const high = hybrid_high * bucket_count;
151 if (zone_estimate <= low) {
152 return low_estimate;
153 }
154 if (zone_estimate >= high) {
155 return corrected_mean;
156 }
157 auto const t = cuda::std::log(zone_estimate / low) / cuda::std::log(high / low);
158 return (1.0 - t) * low_estimate + t * corrected_mean;
159}
constexpr double hybrid_low
constexpr double hybrid_high

◆ hybrid_cardinality_kernel()

template<size_t BucketCount, typename Layout = default_register_layout>
__global__ void cuddl::detail::hybrid_cardinality_kernel ( uint32_t const *const  registers,
hybrid_cardinality_estimates *const  estimates 
)

Computes BBTools and paper-style HybridDDL estimates in one register scan.

Definition at line 2575 of file kernels.cuh.

2578 {
2579 __shared__ uint32_t bins[nlz_bins];
2580 using block_histogram =
2581 cub::BlockHistogram<uint32_t, block_size, 1, nlz_bins, cub::BLOCK_HISTO_ATOMIC>;
2582 using block_reduce = cub::BlockReduce<float, block_size>;
2583 __shared__ typename block_reduce::TempStorage storage;
2584 block_histogram histogram;
2585 histogram.InitHistogram(bins);
2586 float local_restored = 0.0f;
2587 __syncthreads();
2588
2589 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2590 bucket += blockDim.x) {
2591 auto const stored = static_cast<uint16_t>(__ldcs(&registers[bucket]));
2592 uint32_t bin[1]{
2593 stored == 0U ? 0U : static_cast<uint32_t>(stored >> Layout::mantissa_bits) + 1U
2594 };
2595 histogram.Composite(bin, bins);
2596 if (stored != 0U) {
2597 local_restored += static_cast<float>(restore<Layout>(stored));
2598 }
2599 }
2600 __syncthreads();
2601 auto const restored = block_reduce(storage).Sum(local_restored);
2602 if (threadIdx.x == 0) {
2603 *estimates = hybrid_estimates_f32(bins, static_cast<float>(BucketCount), restored);
2604 }
2605}
__device__ hybrid_cardinality_estimates hybrid_estimates_f32(uint32_t const *bins, float bucket_count, float restored_sum) noexcept
Single-precision hybrid_estimates for the latency-bound hybrid scan.
Here is the call graph for this function:

◆ hybrid_cardinality_variant_kernel()

template<size_t BucketCount, hybrid_variant Variant, typename Layout = default_register_layout>
__global__ void cuddl::detail::hybrid_cardinality_variant_kernel ( uint32_t const *const  registers,
double *const  estimate 
)

Computes one HybridDDL variant estimate for estimator comparisons.

Definition at line 2610 of file kernels.cuh.

2610 {
2611 __shared__ uint32_t bins[nlz_bins];
2612 using block_histogram =
2613 cub::BlockHistogram<uint32_t, block_size, 1, nlz_bins, cub::BLOCK_HISTO_ATOMIC>;
2614 using block_reduce = cub::BlockReduce<float, block_size>;
2615 __shared__ typename block_reduce::TempStorage storage;
2616 block_histogram histogram;
2617 histogram.InitHistogram(bins);
2618 float local_restored = 0.0f;
2619 __syncthreads();
2620
2621 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
2622 bucket += blockDim.x) {
2623 auto const stored = static_cast<uint16_t>(__ldcs(&registers[bucket]));
2624 uint32_t bin[1]{
2625 stored == 0U ? 0U : static_cast<uint32_t>(stored >> Layout::mantissa_bits) + 1U
2626 };
2627 histogram.Composite(bin, bins);
2628 if (stored != 0U) {
2629 local_restored += static_cast<float>(restore<Layout>(stored));
2630 }
2631 }
2632 __syncthreads();
2633 auto const restored = block_reduce(storage).Sum(local_restored);
2634 if (threadIdx.x == 0) {
2635 auto const estimates =
2636 hybrid_estimates_f32(bins, static_cast<float>(BucketCount), restored);
2637 if constexpr (Variant == hybrid_variant::bbtools) {
2638 *estimate = estimates.bbtools;
2639 } else {
2640 *estimate = estimates.paper;
2641 }
2642 }
2643}
Here is the call graph for this function:

◆ hybrid_estimates()

__device__ hybrid_cardinality_estimates cuddl::detail::hybrid_estimates ( uint32_t const *  bins,
double  bucket_count,
double  restored_sum 
)
inlinenoexcept

Fused BBTools, paper, linear-counting, DLC, and raw MeanM estimates.

Definition at line 174 of file detail/hybrid_cardinality.cuh.

174 {
175 auto const empty = static_cast<double>(bins[0]);
176 auto const filled = bucket_count - empty;
177 auto const raw_mean = bbtools_mean_m(bucket_count, filled, restored_sum);
178 auto const lc = lc_min(bins, bucket_count);
179 auto const dlc_estimate = dlc(bins, bucket_count, lc);
180 auto const corrected_mean = raw_mean * interpolate_mean_m_cf(dlc_estimate);
181 return {
182 hybrid(lc, lc, corrected_mean, bucket_count),
183 hybrid(lc, dlc_estimate, corrected_mean, bucket_count),
184 lc,
185 dlc_estimate,
186 raw_mean,
187 };
188}
__device__ double interpolate_mean_m_cf(double estimate) noexcept
Interpolates the MeanM correction factor for estimate from the calibration table.
__device__ double dlc(uint32_t const *bins, double bucket_count, double lc) noexcept
Distributed linear-counting estimate, blended from per-tier estimates by precision.
__device__ double bbtools_mean_m(double bucket_count, double filled, double restored_sum) noexcept
BBTools occupancy-corrected MeanM estimate from restored magnitudes.
__device__ double lc_min(uint32_t const *bins, double bucket_count) noexcept
Linear-counting estimate from the first occupied NLZ tier.
Here is the call graph for this function:

◆ hybrid_estimates_f32()

__device__ hybrid_cardinality_estimates cuddl::detail::hybrid_estimates_f32 ( uint32_t const *  bins,
float  bucket_count,
float  restored_sum 
)
inlinenoexcept

Single-precision hybrid_estimates for the latency-bound hybrid scan.

Definition at line 316 of file detail/hybrid_cardinality.cuh.

316 {
317 auto const empty = static_cast<float>(bins[0]);
318 auto const filled = bucket_count - empty;
319 auto const raw_mean = bbtools_mean_m_f32(bucket_count, filled, restored_sum);
320 auto const lc = lc_min_f32(bins, bucket_count);
321 auto const dlc_estimate = dlc_f32(bins, bucket_count, lc);
322 auto const corrected_mean = raw_mean * interpolate_mean_m_cf_f32(dlc_estimate);
323 return {
324 static_cast<double>(hybrid_f32(lc, lc, corrected_mean, bucket_count)),
325 static_cast<double>(hybrid_f32(lc, dlc_estimate, corrected_mean, bucket_count)),
326 static_cast<double>(lc),
327 static_cast<double>(dlc_estimate),
328 static_cast<double>(raw_mean),
329 };
330}
__device__ float dlc_f32(uint32_t const *bins, float bucket_count, float lc) noexcept
Single-precision dlc for the latency-bound hybrid scan.
__device__ float hybrid_f32(float low_estimate, float zone_estimate, float corrected_mean, float bucket_count) noexcept
Single-precision hybrid for the latency-bound hybrid scan.
__device__ float interpolate_mean_m_cf_f32(float estimate) noexcept
Single-precision variant of the hybrid estimator tail.
__device__ float bbtools_mean_m_f32(float bucket_count, float filled, float restored_sum) noexcept
Single-precision bbtools_mean_m for the latency-bound hybrid scan.
__device__ float lc_min_f32(uint32_t const *bins, float bucket_count) noexcept
Single-precision lc_min for the latency-bound hybrid scan.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hybrid_f32()

__device__ float cuddl::detail::hybrid_f32 ( float  low_estimate,
float  zone_estimate,
float  corrected_mean,
float  bucket_count 
)
inlinenoexcept

Single-precision hybrid for the latency-bound hybrid scan.

Definition at line 285 of file detail/hybrid_cardinality.cuh.

290 {
291 auto const low = static_cast<float>(hybrid_low) * bucket_count;
292 auto const high = static_cast<float>(hybrid_high) * bucket_count;
293 if (zone_estimate <= low) {
294 return low_estimate;
295 }
296 if (zone_estimate >= high) {
297 return corrected_mean;
298 }
299 auto const t = cuda::std::log(zone_estimate / low) / cuda::std::log(high / low);
300 return (1.0f - t) * low_estimate + t * corrected_mean;
301}
Here is the caller graph for this function:

◆ hybrid_posting_ranges_kernel()

template<size_t BucketCount>
__global__ void cuddl::detail::hybrid_posting_ranges_kernel ( uint16_t const *  queries,
uint32_t const *  offsets,
uint32_t const *  bitmap_cells,
uint32_t  bitmap_count,
uint32_t  words,
uint32_t  indexed_buckets,
uint16_t  key_mask,
uint2 *  ranges,
uint32_t *  bitmap_starts,
uint32_t *  bitmap_counts 
)

Splits each query's posting ranges into short lists and bitmap offsets.

With no dense offsets, consumes sparse ranges already staged in ranges. Packs bitmap offsets in bucket order so nearby queries walk their shared bitmaps together.

Definition at line 775 of file kernels.cuh.

786 {
787 using block_scan = cub::BlockScan<uint32_t, block_size>;
788 __shared__ typename block_scan::TempStorage storage;
789 uint32_t count = 0U;
790 auto const query = static_cast<uint32_t>(blockIdx.x);
791 auto const base = static_cast<size_t>(query) * indexed_buckets;
792 for (uint32_t first = 0U; first < indexed_buckets; first += block_size) {
793 auto const bucket = first + threadIdx.x;
794 uint2 range{0U, 0U};
795 uint32_t bitmap = 0U;
796 bool selected = false;
797 auto const score = bucket < indexed_buckets
798 ? queries[static_cast<size_t>(query) * BucketCount + bucket]
799 : uint16_t{0};
800 if (score != 0U) {
801 auto const cell = bucket * (static_cast<uint32_t>(key_mask) + 1U) + (score & key_mask);
802 if (offsets != nullptr) {
803 range = {offsets[cell], offsets[cell + 1U]};
804 } else {
805 range = ranges[base + bucket];
806 }
807 if (range.y - range.x >= words) {
808 uint32_t low = 0U, high = bitmap_count;
809 while (low < high) {
810 auto const middle = low + (high - low) / 2U;
811 if (bitmap_cells[middle] < cell) {
812 low = middle + 1U;
813 } else {
814 high = middle;
815 }
816 }
817 bitmap = low * words;
818 selected = true;
819 range = {0U, 0U};
820 }
821 }
822 uint32_t position = 0U, aggregate = 0U;
823 block_scan(storage).ExclusiveSum(static_cast<uint32_t>(selected), position, aggregate);
824 if (selected) {
825 bitmap_starts[base + count + position] = bitmap;
826 }
827 if (bucket < indexed_buckets) {
828 ranges[base + bucket] = range;
829 }
830 count += aggregate;
832 }
833 if (threadIdx.x == 0U) {
835 }
836}
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t uint32_t uint16_t const uint2 const * ranges
Definition kernels.cuh:925
Here is the call graph for this function:

◆ if() [1/5]

cuddl::detail::if ( block >=static_cast< uint64_t >(row_words) *  word_count)

◆ if() [2/5]

cuddl::detail::if ( candidate_bits !  = nullptr && all_to_all && high <= query_id_offset + query_index + 1U)

Definition at line 942 of file kernels.cuh.

942 {
943 auto* bits = candidate_bits +
944 static_cast<size_t>(query_index) * candidate_bit_words(reference_count) +
945 low / warp_width;
946 for (auto i = threadIdx.x; i < (high - low + warp_width - 1U) / warp_width;
947 i += blockDim.x) {
948 bits[i] = 0U;
949 }
950 return;
951 }
__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 bool uint32_t * candidate_bits
Definition kernels.cuh:929

◆ if() [3/5]

cuddl::detail::if ( candidate_bits  = = nullptr)

Definition at line 1174 of file kernels.cuh.

1174 {
1175 auto* const counts =
1176 match_counts + static_cast<size_t>(query_index) * reference_count + low;
1177 for (auto i = threadIdx.x; i < width; i += blockDim.x) {
1178 counts[i] = (packed_counts[i / 2U] >> ((i % 2U) * 16U)) & 0xffffU;
1179 }
1180 return;
1181 }
static __global__ uint32_t const uint32_t uint32_t const uint32_t * match_counts
Definition kernels.cuh:576

◆ if() [4/5]

cuddl::detail::if ( local !  = 0U)

Definition at line 499 of file kernels.cuh.

499 {
500 atomicAdd(work, local);
501 }

◆ if() [5/5]

cuddl::detail::if ( query >=tile.  query_count)

◆ index_tile_references()

uint32_t cuddl::detail::index_tile_references ( uint32_t  reference_count,
uint32_t  query_count,
uint32_t  multiprocessors,
uint32_t  max_shared_bytes 
)
inlinenoexcept

References per tile, bounded by the device's shared memory and small enough that the batch launches about four blocks per SM.

Tiles align to 64 references.

Definition at line 702 of file kernels.cuh.

707 {
708 constexpr uint32_t static_bytes = index_tile_long_capacity * sizeof(uint2) + sizeof(uint32_t);
709 auto const max_references = (max_shared_bytes - static_bytes) / (64U * sizeof(uint16_t)) * 64U;
710 auto const needed = (reference_count + max_references - 1U) / max_references;
711 auto const filling = (4U * multiprocessors + query_count - 1U) / query_count;
712 auto const tiles = needed > filling ? needed : filling;
713 return ((reference_count + tiles - 1U) / tiles + 63U) / 64U * 64U;
714}
__global__ size_t uint32_t query_count
Definition kernels.cuh:600
constexpr uint32_t index_tile_long_capacity
Long posting ranges one count_batch_index_tile_kernel block walks together.
Definition kernels.cuh:685
auto const tiles
Definition kernels.cuh:937

◆ indexed_cell_count()

constexpr uint64_t cuddl::detail::indexed_cell_count ( score_compatibility const &  compatibility)
inlineconstexprnoexcept

Definition at line 921 of file reference_database.cuh.

923 {
924 return static_cast<uint64_t>(compatibility.indexed_bucket_count) *
925 (static_cast<uint64_t>(compatibility.key_mask) + 1U);
926}
Here is the caller graph for this function:

◆ indexed_posting_count()

constexpr uint64_t cuddl::detail::indexed_posting_count ( uint32_t  reference_count,
score_compatibility const &  compatibility 
)
inlineconstexprnoexcept

Definition at line 928 of file reference_database.cuh.

928 {
929 return static_cast<uint64_t>(reference_count) * compatibility.indexed_bucket_count;
930}
Here is the caller graph for this function:

◆ inflate_and_pack()

Result< std::optional< std::string_view > > cuddl::detail::inflate_and_pack ( fastx_sequence_file &  result,
std::string_view  input,
std::string const &  path,
decompression_source  source,
uint32_t  k 
)
inline

Inflates gzip input in this thread's reusable heap buffer and, for FASTA, packs it there and moves only the packed words into a buffer from source.

The words are what crosses the bus, a quarter of the bases, so only they need page-locked memory. Pinning the whole inflated genome made pinning, which the driver runs one call at a time, what a fresh process with many loaders waited on. FASTQ is copied whole instead, and its bytes are returned for the parser; packed FASTA returns nothing.

Definition at line 1615 of file fastx_sequence_file.hpp.

1621 {
1622 thread_local std::unique_ptr<char[]> scratch;
1623 thread_local size_t capacity = 0;
1624 // A single-member file's ISIZE is its inflated length. ISIZE sizes nothing for a BGZF stream,
1625 // which ends in an empty EOF block that reads back as zero, so a hint below the compressed
1626 // size falls back to an over-estimate; a short buffer doubles and inflates again.
1627 auto const hint = gzip_size_hint(input);
1628 size_t wanted = hint >= input.size() ? hint : input.size() * 8;
1629 size_t produced = 0;
1630 while (true) {
1631 if (capacity < wanted) {
1632 capacity = std::bit_ceil(wanted);
1633 scratch = std::make_unique_for_overwrite<char[]>(capacity);
1634 }
1635 bool insufficient = false;
1636 produced = CUDDL_TRY(
1637 gunzip_members_direct(input, {scratch.get(), capacity, {}}, insufficient, path)
1638 );
1639 if (!insufficient) break;
1640 wanted = capacity * 2;
1641 }
1642 std::string_view const data{scratch.get(), produced};
1643 auto const first = data.find_first_not_of("\r\n");
1644 bool const fastq = first != std::string_view::npos && data[first] == '@';
1645 if (!fastq) CUDDL_TRY(pack_fasta_chunks(result, data, scratch.get(), k, path));
1646 uint64_t bytes = produced;
1647 if (!fastq) {
1648 uint64_t words = 0;
1649 for (auto const& record : result.packed) {
1650 words = std::max(words, record.word + (record.bases + 15) / 16);
1651 }
1652 bytes = words * sizeof(uint32_t);
1653 }
1654 auto target = source.request(bytes);
1655 char* out = target.data;
1656 if (out == nullptr) {
1657 result.decompressed.resize(bytes);
1658 out = result.decompressed.data();
1659 } else {
1660 result.decompressed_target = out;
1661 result.decompressed_size = bytes;
1662 result.storage_owner = std::move(target.owner);
1663 }
1664 std::memcpy(out, scratch.get(), bytes);
1665 if (fastq) return std::optional{std::string_view{out, bytes}};
1666 result.words = reinterpret_cast<uint32_t const*>(out);
1667 return std::optional<std::string_view>{};
1668}
size_t gzip_size_hint(std::string_view input)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ interpolate_mean_m_cf()

__device__ double cuddl::detail::interpolate_mean_m_cf ( double  estimate)
inlinenoexcept

Interpolates the MeanM correction factor for estimate from the calibration table.

Definition at line 59 of file detail/hybrid_cardinality.cuh.

59 {
60 if (estimate <= ddl_cf_keys[0]) {
61 return ddl_mean_m_cf[0];
62 }
63 if (estimate >= ddl_cf_keys[ddl_cf_size - 1]) {
64 return ddl_mean_m_cf[ddl_cf_size - 1];
65 }
66 uint32_t low = 0;
67 uint32_t high = ddl_cf_size - 1;
68 while (low + 1 < high) {
69 auto const middle = (low + high) / 2;
70 if (ddl_cf_keys[middle] <= estimate) {
71 low = middle;
72 } else {
73 high = middle;
74 }
75 }
76 auto const fraction = (estimate - ddl_cf_keys[low]) / (ddl_cf_keys[high] - ddl_cf_keys[low]);
77 return ddl_mean_m_cf[low] + fraction * (ddl_mean_m_cf[high] - ddl_mean_m_cf[low]);
78}
Here is the caller graph for this function:

◆ interpolate_mean_m_cf_f32()

__device__ float cuddl::detail::interpolate_mean_m_cf_f32 ( float  estimate)
inlinenoexcept

Single-precision variant of the hybrid estimator tail.

The 2,048-register hybrid scan is memory/latency bound on every GPU, but the serial estimator tail in hybrid_estimates is FP64-heavy. Datacenter GPUs (H100/B200) execute those instructions at half FP32 rate, while consumer cards rate-limit FP64 to 1/64 of FP32. Keeping the scan and tail in FP32 avoids that consumer cliff; the ~1e-7 relative error is far below the ~2% standard error of a 2,048-register sketch.

Definition at line 200 of file detail/hybrid_cardinality.cuh.

200 {
201 if (estimate <= ddl_cf_keys[0]) {
202 return ddl_mean_m_cf[0];
203 }
204 if (estimate >= ddl_cf_keys[ddl_cf_size - 1]) {
205 return ddl_mean_m_cf[ddl_cf_size - 1];
206 }
207 uint32_t low = 0;
208 uint32_t high = ddl_cf_size - 1;
209 while (low + 1 < high) {
210 auto const middle = (low + high) / 2;
211 if (ddl_cf_keys[middle] <= estimate) {
212 low = middle;
213 } else {
214 high = middle;
215 }
216 }
217 auto const fraction = (estimate - ddl_cf_keys[low]) / (ddl_cf_keys[high] - ddl_cf_keys[low]);
218 return ddl_mean_m_cf[low] + fraction * (ddl_mean_m_cf[high] - ddl_mean_m_cf[low]);
219}
Here is the caller graph for this function:

◆ lane_posting_range()

__device__ uint2 cuddl::detail::lane_posting_range ( uint32_t const *  offsets,
uint16_t const *  sorted_keys,
uint32_t  reference_count,
uint32_t  bucket,
uint32_t  key,
uint32_t  key_count 
)
inline

One thread's posting range for a bucket/key cell of a dense or sparse index.

Definition at line 395 of file kernels.cuh.

402 {
403 if (sorted_keys == nullptr) {
404 auto const cell = static_cast<size_t>(bucket) * key_count + key;
405 return {offsets[cell], offsets[cell + 1U]};
406 }
407 auto const* first = sorted_keys + static_cast<size_t>(bucket) * reference_count;
408 auto const sparse_key = static_cast<uint16_t>(key + (key_count == 32768U));
409 auto const range = cuda::std::equal_range(first, first + reference_count, sparse_key);
410 return {
411 static_cast<uint32_t>(range.first - sorted_keys),
412 static_cast<uint32_t>(range.second - sorted_keys)
413 };
414}

◆ launch_construction()

template<size_t BucketCount, typename Layout = default_register_layout>
__host__ Result< void > cuddl::detail::launch_construction ( device_span< uint64_t const >  input,
device_span< uint32_t >  registers,
cuda::stream_ref  stream,
cuda::std::optional< blacklist_view >  blacklist = cuda::std::nullopt 
)
inline

Launches construction of a sketch through a CTA-local shared register array merged into registers (add_shared_kernel).

Definition at line 17 of file construction.cuh.

22 {
23 if (input.empty()) {
24 return {};
25 }
26 return cuda_try([&] {
27 auto const multiprocessors =
28 stream.device().attribute(cuda::device_attributes::multiprocessor_count);
29 auto const vector_input = (reinterpret_cast<uintptr_t>(input.data()) & 31U) == 0U;
30 // Two CTAs per SM; the grid-stride loop covers the remaining inputs.
31 auto const capacity = static_cast<size_t>(shared_construction_block_size) * 4U;
32 auto const needed = input.size() / capacity + (input.size() % capacity != 0U);
33 auto const blocks =
34 static_cast<uint32_t>(cuda::std::min<size_t>(multiprocessors * 2U, needed));
35 auto launch = [&]<bool Filter>() {
36 add_shared_kernel<BucketCount, Layout, Filter>
37 <<<blocks, shared_construction_block_size, 0, stream.get()>>>(
38 input.data(),
39 input.size(),
40 registers.data(),
41 vector_input,
42 blacklist.value_or(blacklist_view{})
43 );
44 };
45 if (blacklist) {
46 launch.template operator()<true>();
47 } else {
48 launch.template operator()<false>();
49 }
50 return cudaGetLastError();
51 });
52}
Here is the call graph for this function:

◆ launch_sequence_add()

template<size_t BucketCount, typename Layout = default_register_layout>
__host__ Result< void > cuddl::detail::launch_sequence_add ( device_span< char const >  sequence,
uint32_t  k,
device_span< uint32_t >  registers,
cuda::stream_ref  stream,
cuda::std::optional< blacklist_view >  blacklist = cuda::std::nullopt 
)
inline

Shared launch for one device-resident ASCII chunk.

Windows derive as size >= k ? size - k + 1 : 0; short input is a no-op. Window counts above UINT32_MAX are rejected instead of narrowing, so size must not exceed UINT32_MAX + k - 1. No hidden carry: the caller supplies any K-1 overlap explicitly.

Definition at line 375 of file sequence_encode.cuh.

381 {
382 if (k < 1 || k > 31) {
383 return Err(Error::invalid_argument("invalid k-mer length for sequence add"));
384 }
385 size_t const size = sequence.size();
386 if (size < k) {
387 return Ok();
388 }
389 size_t const windows_size = size - k + 1;
390 if (windows_size > std::numeric_limits<uint32_t>::max()) {
391 return Err(Error::invalid_argument("sequence chunk exceeds 32-bit window capacity"));
392 }
393 auto const windows = static_cast<uint32_t>(windows_size);
394 auto launch = [&]<bool Filter>() -> Result<void> {
395 return cuda_try([&] {
396 auto const kernel = add_sequence_single_kernel<BucketCount, Layout, Filter>;
397 auto const multiprocessors =
398 stream.device().attribute(cuda::device_attributes::multiprocessor_count);
399 // Fill one resident wave, accounting for the GPU and this sketch's shared memory.
400 if constexpr (Filter) {
401 // Leave L1 room for the blacklist presence map; the default favors shared memory.
402 auto const carveout = cudaFuncSetAttribute(
403 kernel, cudaFuncAttributePreferredSharedMemoryCarveout, 50
404 );
405 if (carveout != cudaSuccess) return carveout;
406 }
407 int blocks_per_sm = 0;
408 auto const occupancy =
409 cudaOccupancyMaxActiveBlocksPerMultiprocessor(&blocks_per_sm, kernel, 256, 0);
410 if (occupancy != cudaSuccess) return occupancy;
411 size_t const need = (static_cast<size_t>(windows) + 2047) / 2048;
412 size_t const capacity = static_cast<size_t>(multiprocessors) * blocks_per_sm;
413 size_t const blocks_size =
414 capacity == 0 ? size_t{1} : (capacity < need ? capacity : need);
415 auto const blocks = static_cast<uint32_t>(blocks_size);
416 kernel<<<blocks, 256, 0, stream.get()>>>(
417 sequence.data(), windows, k, registers.data(), blacklist.value_or(blacklist_view{})
418 );
419 return cudaGetLastError();
420 });
421 };
422 return blacklist ? launch.template operator()<true>() : launch.template operator()<false>();
423}
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.
Definition error.hpp:187
Here is the call graph for this function:

◆ lc_min()

__device__ double cuddl::detail::lc_min ( uint32_t const *  bins,
double  bucket_count 
)
inlinenoexcept

Linear-counting estimate from the first occupied NLZ tier.

Definition at line 81 of file detail/hybrid_cardinality.cuh.

81 {
82 auto cumulative = bins[0];
83 if (cumulative > 0U) {
84 return bucket_count * cuda::std::log(bucket_count / cumulative);
85 }
86 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
87 cumulative += bins[tier + 1];
88 if (cumulative > 0U) {
89 return cuda::std::ldexp(
90 bucket_count * cuda::std::log(bucket_count / cumulative), tier + 1
91 );
92 }
93 }
94 return 0.0;
95}
Here is the caller graph for this function:

◆ lc_min_f32()

__device__ float cuddl::detail::lc_min_f32 ( uint32_t const *  bins,
float  bucket_count 
)
inlinenoexcept

Single-precision lc_min for the latency-bound hybrid scan.

Definition at line 222 of file detail/hybrid_cardinality.cuh.

222 {
223 auto cumulative = bins[0];
224 if (cumulative > 0U) {
225 return bucket_count * cuda::std::log(bucket_count / cumulative);
226 }
227 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
228 cumulative += bins[tier + 1];
229 if (cumulative > 0U) {
230 return cuda::std::ldexp(
231 bucket_count * cuda::std::log(bucket_count / cumulative), tier + 1
232 );
233 }
234 }
235 return 0.0f;
236}
Here is the caller graph for this function:

◆ load_256_alignment()

__host__ __device__ constexpr uint32_t cuddl::detail::load_256_alignment ( )
constexprnoexcept

Byte alignment load_256_global_nc requires on the compiled architecture.

sm_100 and later issue one 256-bit load per call, which needs 32-byte alignment. Earlier architectures issue two 128-bit loads, which need only 16 bytes, so a 16-byte-aligned row still takes the wide path there.

Definition at line 22 of file helpers.cuh.

22 {
23#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 1000
24 return 32U;
25#else
26 return 16U;
27#endif
28}
Here is the caller graph for this function:

◆ load_256_global_nc()

template<typename T >
__device__ __forceinline__ void cuddl::detail::load_256_global_nc ( const T *  ptr,
T *  out 
)

Loads 256 bits from global memory using the non-coherent cache path.

Template Parameters
TElement type (uint32_t or uint64_t)
Parameters
ptrSource pointer (must be aligned to load_256_alignment)
outOutput array (4 elements for uint64_t, 8 for uint32_t)

Definition at line 47 of file helpers.cuh.

47 {
48 static_assert(sizeof(T) == 4 || sizeof(T) == 8, "T must be uint32_t or uint64_t");
49
50#if __CUDA_ARCH__ >= 1000
51 if constexpr (sizeof(T) == 8) {
52 asm volatile("ld.global.nc.v4.u64 {%0, %1, %2, %3}, [%4];"
53 : "=l"(out[0]), "=l"(out[1]), "=l"(out[2]), "=l"(out[3])
54 : "l"(ptr));
55 } else {
56 asm volatile("ld.global.nc.v8.u32 {%0, %1, %2, %3, %4, %5, %6, %7}, [%8];"
57 : "=r"(out[0]),
58 "=r"(out[1]),
59 "=r"(out[2]),
60 "=r"(out[3]),
61 "=r"(out[4]),
62 "=r"(out[5]),
63 "=r"(out[6]),
64 "=r"(out[7])
65 : "l"(ptr));
66 }
67#else
68 if constexpr (sizeof(T) == 8) {
69 asm volatile("ld.global.nc.v2.u64 {%0, %1}, [%2];" : "=l"(out[0]), "=l"(out[1]) : "l"(ptr));
70 asm volatile("ld.global.nc.v2.u64 {%0, %1}, [%2];"
71 : "=l"(out[2]), "=l"(out[3])
72 : "l"(ptr + 2));
73 } else {
74 asm volatile("ld.global.nc.v4.u32 {%0, %1, %2, %3}, [%4];"
75 : "=r"(out[0]), "=r"(out[1]), "=r"(out[2]), "=r"(out[3])
76 : "l"(ptr));
77 asm volatile("ld.global.nc.v4.u32 {%0, %1, %2, %3}, [%4];"
78 : "=r"(out[4]), "=r"(out[5]), "=r"(out[6]), "=r"(out[7])
79 : "l"(ptr + 4));
80 }
81#endif
82}
Here is the caller graph for this function:

◆ load_chunk()

template<typename T >
__host__ __device__ __forceinline__ T cuddl::detail::load_chunk ( const uint8_t *  data,
uint64_t  index 
)

Loads a chunk of type T from data at byte offset index*sizeof(T).

Definition at line 46 of file hash.cuh.

46 {
47 T chunk;
48 memcpy(&chunk, data + index * sizeof(T), sizeof(T));
49 return chunk;
50}

◆ load_fastx_sequence_file()

Result< std::unique_ptr< fastx_sequence_file > > cuddl::detail::load_fastx_sequence_file ( std::string const &  path,
decompression_source  source = {},
bool  defer_fasta = false,
bool  read = false,
uint32_t  pack_k = 0 
)
inline

Definition at line 1738 of file fastx_sequence_file.hpp.

1740 {},
1741 bool defer_fasta = false,
1742 bool read = false,
1743 uint32_t pack_k = 0
1744) {
1745#if defined(__linux__)
1746 if (source.acquire != nullptr) {
1747 int const fd = ::open(path.c_str(), O_RDONLY);
1748 struct stat status{};
1749 if (fd == -1 || ::fstat(fd, &status) != 0 || status.st_size < 0) {
1750 if (fd != -1) ::close(fd);
1751 return Err(Error::invalid_argument("cannot open FASTX file: " + path));
1752 }
1753 auto const size = static_cast<size_t>(status.st_size);
1754 unsigned char magic[2] = {};
1755 bool const gzip = ::pread(fd, magic, sizeof(magic), 0) == sizeof(magic) &&
1756 magic[0] == 0x1f && magic[1] == 0x8b;
1757 // Plain bytes go straight into the caller's buffer, as inflated bytes do: the parser
1758 // compacts them in place and the stager reads them without another copy.
1759 auto target = gzip ? decompression_target{} : source.request(size);
1760 if (target.data == nullptr) {
1761 auto file = fastx_mapped_file::adopt(fd, size, path, read, source.direct_reads);
1762 if (!file) return Err(Error::invalid_argument("cannot open FASTX file: " + path));
1763 return decode_fastx_sequence_file(std::move(*file), path, source, defer_fasta, pack_k);
1764 }
1765 if (!read_whole_file(fd, target.data, size, source.direct_reads, target.capacity)) {
1766 return Err(Error::invalid_argument("cannot open FASTX file: " + path));
1767 }
1768 auto result = std::make_unique<fastx_sequence_file>();
1769 result->decompressed_target = target.data;
1770 result->decompressed_size = size;
1771 result->storage_owner = std::move(target.owner);
1773 std::move(result), {target.data, size}, path, defer_fasta, pack_k
1774 );
1775 }
1776#endif
1777 auto file = fastx_mapped_file::load(path, read, source.direct_reads);
1778 if (!file) return Err(Error::invalid_argument("cannot open FASTX file: " + path));
1779 return decode_fastx_sequence_file(std::move(*file), path, source, defer_fasta, pack_k);
1780}
Result< std::unique_ptr< fastx_sequence_file > > decode_fastx_sequence_file(std::unique_ptr< fastx_mapped_file > file, std::string const &path, decompression_source source, bool defer_fasta, uint32_t pack_k=0)
bool direct_reads
Read files with O_DIRECT, as read_whole_file does.
Here is the caller graph for this function:

◆ load_plane_chunk()

__device__ __forceinline__ void cuddl::detail::load_plane_chunk ( uint4 const *  row,
uint32_t  group,
uint32_t  chunk,
uint32_t(&)  words[4] 
)

Loads plane chunk chunk (planes 4 chunk ... 4 chunk + 3) of group group of a bit-plane row (score_plane_index layout).

Definition at line 2101 of file kernels.cuh.

2101 {
2102 auto const value = row[((group / 32U) * 4U + chunk) * 32U + group % 32U];
2103 words[0] = value.x;
2104 words[1] = value.y;
2105 words[2] = value.z;
2106 words[3] = value.w;
2107}
constexpr uint32_t group
Definition kernels.cuh:1858
Here is the caller graph for this function:

◆ load_plane_group()

__device__ __forceinline__ void cuddl::detail::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).

Definition at line 1380 of file kernels.cuh.

1380 {
1381 _Pragma("unroll")
1382 for (uint32_t c = 0; c < score_planes / 4U; ++c) {
1383 auto const v = row[((group / 32U) * 4U + c) * 32U + group % 32U];
1384 words[4U * c] = v.x;
1385 words[4U * c + 1U] = v.y;
1386 words[4U * c + 2U] = v.z;
1387 words[4U * c + 3U] = v.w;
1388 }
1389}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ load_shared_relaxed()

__device__ __forceinline__ uint32_t cuddl::detail::load_shared_relaxed ( uint32_t const *  address)

Relaxed CTA-scope load of a shared word that other threads update atomically.

cuda::atomic_ref emits a generic-address load here; the shared state space avoids the address conversion and the 64-bit address registers.

Definition at line 29 of file sequence_encode.cuh.

29 {
30 uint32_t value;
31 asm volatile("ld.relaxed.cta.shared.u32 %0, [%1];"
32 : "=r"(value)
33 : "r"(static_cast<uint32_t>(__cvta_generic_to_shared(address)))
34 : "memory");
35 return value;
36}

◆ make_refine_cells()

template<bool UpperTriangle>
__device__ __forceinline__ refine_cells cuddl::detail::make_refine_cells ( uint32_t  query_id_offset,
uint32_t  query_count,
uint32_t  group,
uint32_t  words_per_query,
uint32_t  max_block_words,
uint32_t  waves 
)

Splits a tile into refine_cells, about waves cells per block when the tile allows, so that blocks claiming cells dynamically even out cells of uneven work.

Every query of an upper-triangle tile follows query_id_offset, so words wholly before it hold no pairs and get no cells; otherwise those empty cells would take most of the split for tiles far down the triangle.

Definition at line 1717 of file kernels.cuh.

1724 {
1725 auto const groups = (query_count + group - 1U) / group;
1726 auto const first_word =
1727 UpperTriangle ? cuda::std::min(query_id_offset / 32U, words_per_query) : 0U;
1728 auto const words = words_per_query - first_word;
1729 auto const blocks_per_group = cuda::std::max(1U, waves * gridDim.x / groups);
1730 auto const block_words = cuda::std::max(
1731 1U, cuda::std::min(max_block_words, (words + blocks_per_group - 1U) / blocks_per_group)
1732 );
1733 return {groups, first_word, block_words, groups * ((words + block_words - 1U) / block_words)};
1734}
auto const groups
Definition kernels.cuh:1886
__global__ uint32_t uint32_t first_word
Definition top_k.cuh:234
auto const words_per_query
Definition kernels.cuh:1795

◆ match_filter_kernel()

template<size_t BucketCount, bool UpperTriangle, uint32_t QueryGroup>
__global__ void cuddl::detail::match_filter_kernel ( uint32_t const *  query_planes,
uint32_t  query_id_offset,
uint32_t  query_count,
uint32_t const *  reference_planes,
uint32_t  reference_count,
uint32_t  indexed_bucket_count,
uint32_t  minimum_matches,
uint32_t *  candidate_bits,
uint32_t *  cell_counter 
)

Records in candidate_bits (candidate-bitmap layout) the pairs of the layout whose scores agree in their low match_filter_planes planes in at least minimum_matches buckets.

Every validated key mask covers those planes, so the count bounds a pair's index matches from above and the candidates include every pair meeting the threshold. On RefSeq sketches the bound passes barely more pairs than the exact count; a thresholded bitmap refinement then counts matches exactly.

Cells, query staging, and word claiming follow refine_batch_bitmap_kernel. A warp takes a claimed word's references four at a time and keeps their low planes in registers, so each query plane chunk read from shared memory serves four pairs.

Definition at line 2125 of file kernels.cuh.

2135 {
2136 static_assert(match_filter_supported<BucketCount>);
2137 constexpr uint32_t warp_width = 32;
2138 constexpr uint32_t group = QueryGroup;
2139 static_assert(group >= 1U && group <= warp_width);
2140 constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U);
2141 constexpr uint32_t lane_groups = static_cast<uint32_t>(BucketCount / warp_width / warp_width);
2142 constexpr uint32_t max_block_words = static_cast<uint32_t>(cuda::std::max<size_t>(
2143 1U, refine_block_row_bytes / (BucketCount * sizeof(uint16_t)) / warp_width
2144 ));
2145 extern __shared__ uint4 filter_query_planes[];
2146 __shared__ uint32_t next_word;
2147 __shared__ uint32_t claimed_cell;
2148
2149 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
2150 auto const words_per_query = candidate_bit_words(reference_count);
2151 auto const cells = make_refine_cells<UpperTriangle>(
2152 query_id_offset, query_count, group, words_per_query, max_block_words, 1U
2153 );
2154 auto const groups = cells.groups;
2155 auto const indexed_groups = indexed_bucket_count / warp_width;
2156 // Unindexed buckets are seeded as differing, so they never count toward the bound.
2157 uint32_t excluded_lanes[lane_groups];
2158 _Pragma("unroll")
2159 for (uint32_t j = 0; j < lane_groups; ++j) {
2160 excluded_lanes[j] = j * warp_width + lane < indexed_groups ? 0U : ~0U;
2161 }
2162
2163 for (auto cell = next_cell(cell_counter, blockIdx.x - gridDim.x, claimed_cell);
2164 cell < cells.cell_count;
2165 cell = next_cell(cell_counter, cell, claimed_cell)) {
2166 auto const first_query = (cell % groups) * group;
2167 auto const group_size = cuda::std::min(group, query_count - first_query);
2168 auto const first_word = cells.first_word + (cell / groups) * cells.block_words;
2169 auto const last_word = cuda::std::min(first_word + cells.block_words, words_per_query);
2170 auto const* group_planes = reinterpret_cast<uint4 const*>(
2171 query_planes + static_cast<size_t>(first_query) * row_words
2172 );
2173
2174 __syncthreads();
2175 if (threadIdx.x == 0U) {
2176 next_word = first_word;
2177 }
2178 for (auto i = static_cast<uint32_t>(threadIdx.x); i < group_size * (row_words / 4U);
2179 i += match_filter_block_size) {
2180 filter_query_planes[i] = group_planes[i];
2181 }
2182 __syncthreads();
2183
2184 for (;;) {
2185 uint32_t word = 0U;
2186 if (lane == 0U) {
2187 word = atomicAdd(&next_word, 1U);
2188 }
2189 word = __shfl_sync(0xffffffffU, word, 0);
2190 if (word >= last_word) {
2191 break;
2192 }
2193 // Lane m holds query m's selected references in this word.
2194 uint32_t bits = 0U;
2195 if (lane < group_size) {
2196 auto const first_reference = word * warp_width;
2197 auto const left = reference_count - first_reference;
2198 bits = left >= warp_width ? ~0U : (1U << left) - 1U;
2199 if constexpr (UpperTriangle) {
2200 auto const query_id = query_id_offset + first_query + lane;
2201 if (first_reference <= query_id) {
2202 auto const skipped = query_id - first_reference + 1U;
2203 bits &= skipped >= warp_width ? 0U : ~0U << skipped;
2204 }
2205 }
2206 }
2207 uint32_t passed = 0U;
2208 auto remaining = __reduce_or_sync(0xffffffffU, bits);
2209 constexpr uint32_t refs = 4U;
2210 constexpr uint32_t bound_chunks = match_filter_planes / 4U;
2211 constexpr uint32_t bound_planes = match_filter_planes;
2212 while (remaining != 0U) {
2213 uint32_t bit[refs];
2214 bool has[refs];
2215 uint32_t last_bit = 0U;
2216 _Pragma("unroll")
2217 for (uint32_t i = 0; i < refs; ++i) {
2218 has[i] = remaining != 0U;
2219 bit[i] = has[i] ? static_cast<uint32_t>(__ffs(remaining) - 1) : bit[0];
2220 last_bit = has[i] ? bit[i] : last_bit;
2221 remaining &= remaining - 1U;
2222 }
2223 uint4 const* reference[refs];
2224 uint32_t selecting[refs];
2225 uint32_t r[refs][lane_groups][bound_planes];
2226 _Pragma("unroll")
2227 for (uint32_t i = 0; i < refs; ++i) {
2228 reference[i] = reinterpret_cast<uint4 const*>(
2230 static_cast<size_t>(word * warp_width + bit[i]) * row_words
2231 );
2232 selecting[i] = has[i] ? __ballot_sync(0xffffffffU, (bits >> bit[i]) & 1U) : 0U;
2233 _Pragma("unroll")
2234 for (uint32_t j = 0; j < lane_groups; ++j) {
2235 _Pragma("unroll")
2236 for (uint32_t c = 0; c < bound_chunks; ++c) {
2237 uint32_t words[4];
2238 load_plane_chunk(reference[i], j * warp_width + lane, c, words);
2239 _Pragma("unroll")
2240 for (uint32_t w = 0; w < 4U; ++w) {
2241 r[i][j][4U * c + w] = words[w];
2242 }
2243 }
2244 }
2245 }
2246 // Selections are prefixes of the query group, growing with the reference ID.
2247 auto const members = static_cast<uint32_t>(
2248 __popc(__ballot_sync(0xffffffffU, (bits >> last_bit) & 1U))
2249 );
2250 for (uint32_t m = 0U; m < members; ++m) {
2251 auto const* member =
2252 filter_query_planes + static_cast<size_t>(m) * (row_words / 4U);
2253 uint32_t mismatches[refs] = {};
2254 _Pragma("unroll")
2255 for (uint32_t j = 0; j < lane_groups; ++j) {
2256 uint32_t differ[refs];
2257 _Pragma("unroll")
2258 for (uint32_t i = 0; i < refs; ++i) {
2259 differ[i] = excluded_lanes[j];
2260 }
2261 _Pragma("unroll")
2262 for (uint32_t c = 0; c < bound_chunks; ++c) {
2263 uint32_t q[4];
2264 load_plane_chunk(member, j * warp_width + lane, c, q);
2265 _Pragma("unroll")
2266 for (uint32_t i = 0; i < refs; ++i) {
2267 _Pragma("unroll")
2268 for (uint32_t w = 0; w < 4U; ++w) {
2269 differ[i] |= q[w] ^ r[i][j][4U * c + w];
2270 }
2271 }
2272 }
2273 _Pragma("unroll")
2274 for (uint32_t i = 0; i < refs; ++i) {
2275 mismatches[i] += static_cast<uint32_t>(__popc(differ[i]));
2276 }
2277 }
2278 _Pragma("unroll")
2279 for (uint32_t i = 0; i < refs; ++i) {
2280 auto const total_bound = static_cast<uint32_t>(BucketCount) -
2281 __reduce_add_sync(0xffffffffU, mismatches[i]);
2282 if (lane == m && ((selecting[i] >> m) & 1U) != 0U &&
2283 total_bound >= minimum_matches) {
2284 passed |= 1U << bit[i];
2285 }
2286 }
2287 }
2288 }
2289 if (lane < group_size) {
2290 candidate_bits[static_cast<size_t>(first_query + lane) * words_per_query + word] =
2291 passed;
2292 }
2293 }
2294 }
2295}
constexpr uint32_t row_words
Definition kernels.cuh:1546
constexpr uint32_t max_block_words
Definition kernels.cuh:1867
auto const skipped
Definition top_k.cuh:206
__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 minimum_matches
Definition kernels.cuh:926
__global__ uint32_t const * reference_planes
Definition kernels.cuh:1539
auto const indexed_groups
Definition kernels.cuh:1594
__global__ uint32_t uint32_t indexed_bucket_count
Definition kernels.cuh:438
__device__ __forceinline__ void load_plane_chunk(uint4 const *row, uint32_t group, uint32_t chunk, uint32_t(&words)[4])
Loads plane chunk chunk (planes 4 chunk ... 4 chunk + 3) of group group of a bit-plane row (score_pla...
Definition kernels.cuh:2101
constexpr uint32_t match_filter_planes
Low planes match_filter_kernel compares.
Definition kernels.cuh:2110
__shared__ uint32_t claimed_cell
Definition kernels.cuh:1874
__shared__ uint32_t next_word
Definition kernels.cuh:1873
constexpr uint32_t lane_groups
Definition kernels.cuh:1864
auto const query_id
Definition top_k.cuh:201
Here is the call graph for this function:

◆ mean_m()

__host__ __device__ double cuddl::detail::mean_m ( double  bucket_count,
double  filled,
double  sum_restored 
)
inlinenoexcept

MeanM estimate retained as the full-sketch form of minimum_mle.

Definition at line 45 of file cardinality.cuh.

45 {
46 if (filled <= 0.0 || sum_restored <= 0.0) {
47 return 0.0;
48 }
49 return bucket_count * filled * hash_range / sum_restored;
50}

◆ mean_m_f32()

__host__ __device__ float cuddl::detail::mean_m_f32 ( float  bucket_count,
float  filled,
float  sum_restored 
)
inlinenoexcept

Single-precision mean_m for the throughput-bound cardinality reduction.

Definition at line 82 of file cardinality.cuh.

82 {
83 if (filled <= 0.0f || sum_restored <= 0.0f) {
84 return 0.0f;
85 }
86 return bucket_count * filled * static_cast<float>(hash_range) / sum_restored;
87}

◆ minimum_mle()

__host__ __device__ double cuddl::detail::minimum_mle ( double  bucket_count,
double  empty_count,
double  sum_restored 
)
inlinenoexcept

Estimates the number of distinct hashes from the minimum hash in each bucket.

Each distinct hash lands in one of bucket_count buckets. A bucket therefore receives about cardinality / bucket_count hashes. More hashes make its minimum smaller, so the observed minima tell us how many hashes must have produced them.

The denominator combines all observations after scaling hash values to [0, 1]:

  • each empty bucket contributes 1, meaning that its minimum lies beyond the hash range;
  • each filled bucket contributes its restored interval midpoint divided by hash_range.

Dividing bucket_count * filled_bucket_count by that total gives the maximum-likelihood cardinality estimate. If no bucket is empty, this reduces to the usual MeanM estimate.

Parameters
bucket_countTotal number of sketch buckets.
empty_countNumber of buckets that received no hashes.
sum_restoredSum of the restored interval midpoint from every filled bucket.
Returns
Estimated distinct hash count, or zero when every bucket is empty.

Definition at line 33 of file cardinality.cuh.

33 {
34 auto const filled = bucket_count - empty_count;
35 if (filled <= 0.0) {
36 return 0.0;
37 }
38 return bucket_count * filled / (empty_count + sum_restored / hash_range);
39}
Here is the caller graph for this function:

◆ minimum_mle_f32()

__host__ __device__ float cuddl::detail::minimum_mle_f32 ( float  bucket_count,
float  empty_count,
float  sum_restored 
)
inlinenoexcept

Single-precision variants of the cardinality estimators.

The reduction dominates the cardinality kernel. Keeping the restored-sum reduction and the scalar estimate in FP32 avoids the 1/64 FP32:FP64 throughput ratio on consumer Blackwell while preserving far more precision than the ~2% standard error of a 2,048-register sketch.

Definition at line 71 of file cardinality.cuh.

71 {
72 auto const filled = bucket_count - empty_count;
73 if (filled <= 0.0f) {
74 return 0.0f;
75 }
76 return bucket_count * filled /
77 (empty_count + sum_restored / static_cast<float>(hash_range));
78}
Here is the caller graph for this function:

◆ mpi_try()

Result< void > cuddl::detail::mpi_try ( int  status,
char const *  call 
)
inline

Definition at line 65 of file distributed.cuh.

65 {
66 if (status == MPI_SUCCESS) return Ok();
67 char text[MPI_MAX_ERROR_STRING];
68 int length = 0;
69 MPI_Error_string(status, text, &length);
70 return Err(Error::resource(std::string(call) + ": " + std::string(text, length)));
71}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ next_cell()

__device__ __forceinline__ uint32_t cuddl::detail::next_cell ( uint32_t *  cell_counter,
uint32_t  cell,
uint32_t &  claimed 
)

The cell a refinement-style block processes after cell: claimed from cell_counter, a zeroed per-launch counter, so blocks balance uneven cells; without one, grid-stride.

Called by every thread of the block; claimed is the block's shared slot. The first cell is next_cell(cell_counter, blockIdx.x - gridDim.x, claimed).

Definition at line 1690 of file kernels.cuh.

1690 {
1691 if (cell_counter == nullptr) {
1692 return cell + gridDim.x;
1693 }
1694 __syncthreads();
1695 if (threadIdx.x == 0U) {
1696 claimed = atomicAdd(cell_counter, 1U);
1697 }
1698 __syncthreads();
1699 return claimed;
1700}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ pack_bases16_portable()

uint32_t cuddl::detail::pack_bases16_portable ( char const *  bases,
size_t  count,
uint32_t &  word 
)
inline

Packs up to sixteen bases into a two-bit word, base j in bits 2j and 2j+1.

Codes are those of encode_base. A byte that is not a base gets a meaningless code.

Returns
A mask with bit j set when byte j is not a base, or is past count.

Definition at line 591 of file fastx_sequence_file.hpp.

591 {
592 if (count < 16) {
593 uint32_t bad = 0;
594 word = 0;
595 for (uint32_t j = 0; j < 16; ++j) {
596 auto const symbol = j < count ? encode_base(bases[j]) : uint8_t{0xFF};
597 word |= static_cast<uint32_t>(symbol & 3U) << (2 * j);
598 bad |= static_cast<uint32_t>(symbol == 0xFFU) << j;
599 }
600 return bad;
601 }
602 uint32_t bad = 0;
603 word = 0;
604 for (uint32_t half = 0; half < 2; ++half) {
605 uint64_t x;
606 std::memcpy(&x, bases + 8 * half, sizeof(x));
607 constexpr uint64_t low7 = 0x7F7F7F7F7F7F7F7FULL;
608 // High bit of each byte of v that is zero, without carries between bytes.
609 auto const zero = [](uint64_t v) { return ~(((v & low7) + low7) | v | low7); };
610 auto const upper = x & 0xDFDFDFDFDFDFDFDFULL;
611 auto const valid = zero(upper ^ 0x4141414141414141ULL) |
612 zero(upper ^ 0x4343434343434343ULL) |
613 zero(upper ^ 0x4747474747474747ULL) | zero(upper ^ 0x5454545454545454ULL);
614 auto codes = (x >> 1) & 0x0303030303030303ULL;
615 codes = (codes | (codes >> 6)) & 0x000F000F000F000FULL;
616 codes = (codes | (codes >> 12)) & 0x000000FF000000FFULL;
617 codes = (codes | (codes >> 24)) & 0xFFFFULL;
618 word |= static_cast<uint32_t>(codes) << (16 * half);
619 // Byte j's flag lands on bit j.
620 bad |= static_cast<uint32_t>(
621 ((~valid & 0x8080808080808080ULL) >> 7) * 0x0102040810204080ULL >> 56
622 )
623 << (8 * half);
624 }
625 return bad;
626}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ pack_bases_pieces()

uint64_t cuddl::detail::pack_bases_pieces ( char const *  bases,
size_t  count,
uint32_t *  words,
size_t  width 
)
inline

Packs width bases from sixteen-base pieces, for a tail shorter than a full step.

Definition at line 629 of file fastx_sequence_file.hpp.

629 {
630 uint64_t bad = 0;
631 for (size_t piece = 0; piece < width / 16; ++piece) {
632 auto const left = count > 16 * piece ? count - 16 * piece : 0;
633 bad |= uint64_t{pack_bases16_portable(bases + 16 * piece, left, words[piece])} << (16 * piece);
634 }
635 return bad;
636}
uint32_t pack_bases16_portable(char const *bases, size_t count, uint32_t &word)
Packs up to sixteen bases into a two-bit word, base j in bits 2j and 2j+1.
Here is the call graph for this function:

◆ pack_bases_portable()

uint64_t cuddl::detail::pack_bases_portable ( char const *  bases,
size_t  count,
uint32_t *  words 
)
inline

Definition at line 638 of file fastx_sequence_file.hpp.

638 {
639 return pack_bases16_portable(bases, count, words[0]);
640}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ pack_fasta_chunks()

Result< void > cuddl::detail::pack_fasta_chunks ( fastx_sequence_file &  result,
std::string_view  data,
char *  out,
uint32_t  k,
std::string const &  path 
)
inline

Compact and pack one cache-sized block at a time, without a genome-sized ASCII write.

Blocks end at line boundaries so header detection keeps its line-start invariant. Carrying k - 1 compacted bytes preserves every window without duplicating one.

Definition at line 1508 of file fastx_sequence_file.hpp.

1514 {
1515 size_t header = data.find('>');
1516 while (header != std::string_view::npos && header != 0 && data[header - 1] != '\n' &&
1517 data[header - 1] != '\r') {
1518 header = data.find('>', header + 1);
1519 }
1520 if (header == std::string_view::npos) {
1521 if (!data.empty()) return Err(Error::invalid_argument("FASTX parse error near: " + path));
1522 return Ok();
1523 }
1524 std::string scratch, carry;
1525 fastx_sequence_file chunk;
1526 uint64_t written = 0;
1527 bool has_sequence = false;
1528 auto const* const last = data.data() + data.size();
1529 while (true) {
1530 auto const end = fastx_line_end(data, header);
1531 if (end + 1 >= data.size()) break;
1532 auto const* at = data.data() + end + 1;
1533 carry.clear();
1534 while (at != last) {
1535 auto const offset = static_cast<size_t>(at - data.data());
1536 auto const boundary = offset + std::min<size_t>(16384, data.size() - offset);
1537 auto const limit = boundary == data.size()
1538 ? boundary
1539 : std::min(data.size(), fastx_line_end(data, boundary) + 1);
1540 auto const* const chunk_end = data.data() + limit;
1541 scratch.resize(carry.size() + static_cast<size_t>(chunk_end - at));
1542 std::memcpy(scratch.data(), carry.data(), carry.size());
1543 char const* stop = nullptr;
1544 auto* compact_end =
1545 fasta_sequence_compact(at, chunk_end, scratch.data() + carry.size(), stop);
1546 has_sequence |= stop != at;
1547 auto const bases = static_cast<size_t>(compact_end - scratch.data());
1548 carry.assign(compact_end - std::min<size_t>(k - 1, bases), compact_end);
1549 chunk.extents.push_back({scratch.data(), compact_end});
1550 pack_fastx_sequence_file(chunk, k);
1551 uint64_t words = 0;
1552 for (auto record : chunk.packed) {
1553 words = record.word + (record.bases + 15) / 16;
1554 record.word += written;
1555 result.packed.push_back(record);
1556 }
1557 std::memcpy(out + written * sizeof(uint32_t), scratch.data(), words * sizeof(uint32_t));
1558 written += words;
1559 chunk.packed.clear();
1560 at = stop;
1561 if (stop != chunk_end || stop == last) break;
1562 }
1563 if (at == last) break;
1564 header = static_cast<size_t>(at - data.data());
1565 }
1566 if (!has_sequence && !data.empty()) {
1567 return Err(Error::invalid_argument("FASTX parse error near: " + path));
1568 }
1569 result.words = reinterpret_cast<uint32_t const*>(out);
1570 result.input = {};
1571 return Ok();
1572}
void pack_fastx_sequence_file(fastx_sequence_file &result, uint32_t k)
Packs a parsed file's records in place into two-bit words, run by run.
std::vector< fastx_packed_record > packed
Here is the call graph for this function:
Here is the caller graph for this function:

◆ pack_fastx_sequence_file()

void cuddl::detail::pack_fastx_sequence_file ( fastx_sequence_file &  result,
uint32_t  k 
)
inline

Packs a parsed file's records in place into two-bit words, run by run.

A record splits at every byte that is not a base, and runs shorter than k are dropped: no k-mer holding such a byte is hashed, so the k-mers are those of the records. Output never passes unread input: words are written only after the packer has read their bytes, a full step writes a quarter of what it read, and a partial step writes only for a run of at least k >= 16 bases, whose words take at most half of the bytes it read.

Definition at line 761 of file fastx_sequence_file.hpp.

761 {
762 if (result.extents.empty() || k < 16) return;
763 auto* const out = const_cast<char*>(result.extents.front().begin);
764 uint64_t written = 0;
765 for (auto const& extent : result.extents) {
766 auto const* next = extent.begin;
767 auto const* const end = extent.end;
768 while (next < end) {
769 while (next < end && encode_base(*next) == 0xFF) ++next;
770 auto const* const start = next;
771 auto const first = written;
772 while (next < end) {
773 uint32_t words[4];
774 auto const bad = packer.pack(next, static_cast<size_t>(end - next), words);
775 auto const bases =
776 bad == 0 ? packer.width : static_cast<size_t>(std::countr_zero(bad));
777 // A partial step ends the run; a short run's partial words are never written.
778 if (bases == packer.width || static_cast<size_t>(next + bases - start) >= k) {
779 auto const count = (bases + 15) / 16;
780 std::memcpy(out + written * sizeof(uint32_t), words, count * sizeof(uint32_t));
781 written += count;
782 }
783 next += bases;
784 if (bases != packer.width) break;
785 }
786 auto const bases = static_cast<uint64_t>(next - start);
787 if (bases >= k) {
788 result.packed.push_back({first, bases});
789 } else {
790 written = first;
791 }
792 }
793 }
794 result.words = reinterpret_cast<uint32_t const*>(out);
795 result.extents.clear();
796}
uint64_t(* pack)(char const *bases, size_t count, uint32_t *words)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ pages_locked()

bool cuddl::detail::pages_locked ( transfer_mode  mode,
cuda::device_ref  device 
)
inlinenoexcept

Whether a build in mode decompresses into page-locked buffers.

automatic keys on the device rather than the architecture: a device that reads pageable host memory is coherent, and there the host writes to page-locked memory are the slow part.

Definition at line 99 of file database_staging.cuh.

99 {
100 if (mode == transfer_mode::pinned) return true;
101 if (mode != transfer_mode::automatic) return false;
102 return !device_reads_pageable_memory(device);
103}
bool device_reads_pageable_memory(cuda::device_ref device) noexcept
Whether device reads pageable host memory itself.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ parallel_for()

template<typename Work >
void cuddl::detail::parallel_for ( size_t  count,
size_t  workers,
Work &&  work 
)

Runs work(index) for every index below count on workers threads.

Definition at line 203 of file device_gzip.cuh.

203 {
204 std::atomic<size_t> next{0};
205 auto run = [&] {
206 for (size_t index = next++; index < count; index = next++) work(index);
207 };
208 std::vector<std::jthread> threads;
209 auto const helpers = std::min(workers, count) > 0 ? std::min(workers, count) - 1 : 0;
210 threads.reserve(helpers);
211 for (size_t thread = 0; thread < helpers; ++thread) threads.emplace_back(run);
212 run();
213}

◆ parse_fasta()

Result< fasta_parse_result > cuddl::detail::parse_fasta ( std::string const &  path,
uint32_t  k,
unsigned  threads = std::thread::hardware_concurrency() 
)
inline

Parses a FASTA/FASTQ file's sequence bases into packed canonical k-mers of length k.

Record headers and FASTQ qualities are excluded, and every record's sequence is fed through the DDL rolling-window extraction as one combined genome. An invalid or ambiguous base breaks the rolling window, and no k-mer spanning it is emitted. Each k-mer is canonicalised to the larger packed orientation. Files above 1 MiB are parsed in parallel: the sequence stream is split into seeded spans (fastx_split_sequence_spans), each worker replays its window prefix and then consumes its segments, and the per-span results concatenate to the byte-identical single-threaded output.

Parameters
pathFASTA/FASTQ file to parse.
kK-mer length.
threadsWorker count for parallel files, defaulting to std::thread::hardware_concurrency(); files at or below 1 MiB parse serially regardless.
Returns
A fasta_parse_result, or an error if the file cannot be opened, mapped, or contains bytes but no FASTA records, or malformed FASTQ records.

Definition at line 288 of file fasta_parser.hpp.

292 {
293 auto source = CUDDL_TRY(load_fastx_sequence_file(path));
294 auto& extents = source->extents;
295 if (extents.empty()) return fasta_parse_result{};
296
297 // The span splitter otherwise joins extents and seeds windows across record headers.
298 // A separate invalid-base extent stops prefix replay. Workers recognize its address and
299 // reset without counting the synthetic separator as an invalid input window.
300 static constexpr char record_boundary = 'N';
301 if (extents.size() > 1) {
302 std::vector<fastx_sequence_extent> separated;
303 separated.reserve(extents.size() * 2 - 1);
304 for (auto const& extent : extents) {
305 if (!separated.empty()) separated.push_back({&record_boundary, &record_boundary + 1});
306 separated.push_back(extent);
307 }
308 extents = std::move(separated);
309 }
310
311 uint64_t total = 0;
312 for (auto const& extent : extents) {
313 total += static_cast<uint64_t>(extent.end - extent.begin);
314 }
315
316 unsigned worker_count = 1;
317 if (total >= (uint64_t{1} << 20)) {
318 auto const available = threads > 0 ? threads : 1U;
319 worker_count = available > 0 ? available : 1U;
320 }
321
322 auto const spans = fastx_split_sequence_spans(extents, k > 0 ? k - 1U : 0U, worker_count);
323
324 auto const mask = (k >= 32) ? ~uint64_t{0} : ((1ULL << (2U * k)) - 1ULL);
325 std::vector<span_result> partials(spans.size());
326 auto const per_thread = total / spans.size() + (total % spans.size() != 0U);
327 for (auto& partial : partials) {
328 partial.kmers.reserve(per_thread / 2U + 64U);
329 }
330
331 std::vector<std::thread> workers;
332 workers.reserve(spans.size());
333 for (size_t t = 0; t < spans.size(); ++t) {
334 workers.emplace_back([&, t] {
335 uint64_t window = 0;
336 uint32_t window_len = 0;
337 span_result sink{}; // prefix replay: no counts, no emissions
338 for (char ch : spans[t].prefix) {
339 consume_byte(ch, k, mask, sink, window, window_len);
340 }
341 for (auto const& segment : spans[t].segments) {
342 if (segment.data() == &record_boundary) {
343 window = 0;
344 window_len = 0;
345 continue;
346 }
347 for (char ch : segment) {
348 consume_byte(ch, k, mask, partials[t], window, window_len);
349 }
350 }
351 });
352 }
353 for (auto& worker : workers) {
354 worker.join();
355 }
356
357 fasta_parse_result result;
358 uint64_t total_valid = 0;
359 for (auto const& partial : partials) {
360 result.bases += partial.bases;
361 total_valid += partial.valid_kmers;
362 result.invalid_windows += partial.invalid_windows;
363 }
364 result.valid_kmers = total_valid;
365 // One contiguous allocation for the final stream: reserve the exact count, then move each
366 // partial in and release it immediately so the peak never exceeds the final size plus one
367 // partial (the machine's working set is the result stream itself).
368 result.kmers.reserve(total_valid);
369 for (auto& partial : partials) {
370 result.kmers.insert(
371 result.kmers.end(),
372 std::make_move_iterator(partial.kmers.begin()),
373 std::make_move_iterator(partial.kmers.end())
374 );
375 partial.kmers = std::vector<uint64_t>{};
376 }
377 return result;
378}
Result< std::unique_ptr< fastx_sequence_file > > load_fastx_sequence_file(std::string const &path, decompression_source source={}, bool defer_fasta=false, bool read=false, uint32_t pack_k=0)
void consume_byte(char ch, uint32_t k, uint64_t mask, span_result &result, uint64_t &window, uint32_t &window_len)
Feeds one byte of sequence through the DDL rolling-window extraction.
std::vector< fastx_sequence_span > fastx_split_sequence_spans(std::vector< fastx_sequence_extent > const &extents, uint32_t seed_bases, uint32_t count)
Splits the combined FASTA sequence stream into count contiguous spans.
detail::fasta_parse_result fasta_parse_result
Result of parsing one FASTA/FASTQ file into packed canonical k-mers.
Definition fastx.hpp:16
Result of parsing one FASTA/FASTQ file into packed canonical k-mers.
uint64_t bases
Total valid sequence bases read.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ parse_fastx_sequence_file()

Result< void > cuddl::detail::parse_fastx_sequence_file ( fastx_sequence_file &  result,
std::string const &  path 
)
inline

Definition at line 1470 of file fastx_sequence_file.hpp.

1470 {
1471 auto const data = std::exchange(result.input, {});
1472 auto& sequence = result.sequence;
1473 auto& extents = result.extents;
1474 auto const first = data.find_first_not_of("\r\n");
1475 if (first != std::string_view::npos && data[first] == '@') {
1476 fastx_fastq_reader reader{data, path};
1477 std::vector<std::pair<size_t, size_t>> records;
1478 while (true) {
1479 auto const begin = sequence.size();
1480 auto next = reader.append_next_record(sequence);
1481 if (!next) return Err(Error::invalid_argument(next.error().message()));
1482 if (!*next) break;
1483 if (sequence.size() > begin) records.emplace_back(begin, sequence.size());
1484 }
1485 if (sequence.empty()) return Ok();
1486 for (auto const& [begin, end] : records) {
1487 extents.push_back({sequence.data() + begin, sequence.data() + end});
1488 }
1490 return Ok();
1491 }
1492 // Mapped plain bytes are read-only; decompressed bytes can be compacted in place.
1493 char* out = result.decompressed_target;
1494 if (out == nullptr) {
1495 if (data.data() != result.decompressed.data()) result.decompressed.resize(data.size());
1496 out = result.decompressed.data();
1497 }
1498 extents = fastx_fasta_compact_extents(data, out);
1499 if (extents.empty() && !data.empty()) {
1500 return Err(Error::invalid_argument("FASTX parse error near: " + path));
1501 }
1502 return Ok();
1503}
FASTQ parser over a contiguous in-memory buffer.
Result< bool > append_next_record(std::string &sequence)
Appends the next record's sequence bytes to sequence.
std::vector< fastx_sequence_extent > fastx_fasta_compact_extents(std::string_view data, char *out)
Collects FASTA sequence extents with whitespace compacted out, in one pass over data.
void compact_fastx_sequence_extents(fastx_sequence_file &result)
Compacts sequence whitespace in place, leaving every extent a run of bases.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ plan_staging()

template<uint32_t K, size_t BucketCount>
Result< staging_plan > cuddl::detail::plan_staging ( size_t  references,
uint64_t  staged_ceiling,
std::optional< size_t >  staging_bytes,
cuda::stream_ref  stream 
)
inline

Sizes the arena and the row store from free device memory and the corpus.

staged_ceiling bounds the bytes the corpus can stage, which keeps a small collection from reserving an arena it could never fill. An unset staging_bytes sizes the arena from what is free; a value is honoured, or rejected when it does not fit.

Definition at line 143 of file database_staging.cuh.

148 {
149 size_t const available = CUDDL_TRY(available_device_bytes(stream));
150 size_t const usable = available - available / 10;
151 size_t const row_bytes = BucketCount * sizeof(uint32_t);
152 // Rows are small next to the input, so the whole collection stays resident whenever that
153 // costs at most a quarter of what is affordable; otherwise rows stream back one group at a
154 // time as each group completes.
155 size_t const group = std::min(references, std::max<size_t>(1, usable / (4 * row_bytes)));
156 // Four fifths of what is affordable goes to the arena, which leaves room for the row store,
157 // the descriptors and whatever the caller keeps on the device. On a coherent CPU/GPU system
158 // the free memory reported here is host memory, so a share of host memory bounds it as well.
159 auto const pages = ::sysconf(_SC_PHYS_PAGES);
160 auto const page = ::sysconf(_SC_PAGE_SIZE);
161 size_t const host_share = pages > 0 && page > 0
162 ? static_cast<size_t>(pages) * static_cast<size_t>(page) / 8
163 : std::numeric_limits<size_t>::max();
164 size_t const arena_ceiling = static_cast<size_t>(std::min(
165 {staged_ceiling,
166 static_cast<uint64_t>(usable - usable / 5),
167 static_cast<uint64_t>(host_share),
168 static_cast<uint64_t>(default_arena_ceiling)}
169 ));
170 size_t const row_store_bytes = group * row_bytes;
171 size_t const after_rows = usable > row_store_bytes ? usable - row_store_bytes : 0;
172 if (staging_bytes.has_value() && *staging_bytes < K + 1) {
173 return Err(Error::invalid_argument("an explicit staging arena must hold one window"));
174 }
175 // Every arena gets the same share: one fills while kernels read the others.
176 size_t const staging = staging_bytes.has_value()
177 ? *staging_bytes
178 : std::min(after_rows / stager_arenas, arena_ceiling);
179 // One piece of at least k bases plus its k - 1 byte overlap has to fit.
180 if (staging < K + 1 || staging > after_rows / stager_arenas) {
181 return Err(
182 Error::resource(
183 "insufficient free device memory for reference staging (free " +
184 std::to_string(available >> 20) + " MiB)"
185 )
186 );
187 }
188 // One descriptor per staged piece; the cap keeps a corpus of very short records from
189 // demanding a descriptor per record.
190 size_t const max_pieces = std::min<size_t>(size_t{1} << 20, std::max<size_t>(64, staging / K));
191 // Pieces are a quarter of the arena at most, so a batch always holds several, and short
192 // enough that a piece's window count stays representable.
193 size_t const max_piece = std::min(
194 {std::max<size_t>(K, staging / 4),
195 static_cast<size_t>(std::numeric_limits<uint32_t>::max()) - K + 1}
196 );
197 return staging_plan{group, staging, max_piece, max_pieces};
198}
constexpr size_t default_arena_ceiling
Default arena ceiling.
Result< size_t > available_device_bytes(cuda::stream_ref stream)
Device bytes an allocation on stream can still get.
Here is the call graph for this function:

◆ plane_any()

__device__ __forceinline__ uint32_t cuddl::detail::plane_any ( uint32_t const (&)  planes[score_planes])
noexcept

Buckets of a plane group holding a nonzero score.

Definition at line 1421 of file kernels.cuh.

1421 {
1422 uint32_t any = 0U;
1423 _Pragma("unroll")
1424 for (uint32_t p = 0; p < score_planes; ++p) {
1425 any |= planes[p];
1426 }
1427 return any;
1428}
__global__ uint32_t uint32_t * planes
Definition kernels.cuh:1358
Here is the call graph for this function:
Here is the caller graph for this function:

◆ plane_low_any()

__device__ __forceinline__ uint32_t cuddl::detail::plane_low_any ( uint32_t const (&)  planes[score_planes])
noexcept

Buckets of a plane group whose score is nonzero below the top plane.

Definition at line 1432 of file kernels.cuh.

1432 {
1433 uint32_t any = 0U;
1434 _Pragma("unroll")
1435 for (uint32_t p = 0; p + 1U < score_planes; ++p) {
1436 any |= planes[p];
1437 }
1438 return any;
1439}
Here is the call graph for this function:

◆ probe_gzip_file()

gzip_file_probe cuddl::detail::probe_gzip_file ( std::filesystem::path const &  path)
inline

Reads the header and trailer of path, and of every block of a BGZF file.

A candidate is a gzip file without FEXTRA whose trailer names a non-empty member, a BGZF file with a non-empty block, or an uncompressed file that opens with a FASTA header, any of them with a name that does not mark it FASTQ. For gzip without FEXTRA, additional member signatures are checked after reading the compressed bytes, before accepting the device output.

Definition at line 135 of file device_gzip.cuh.

135 {
136 gzip_file_probe probe;
137 int const fd = ::open(path.c_str(), O_RDONLY);
138 if (fd == -1) return probe;
139 struct stat status{};
140 unsigned char header[18] = {};
141 unsigned char trailer[8 + 18] = {}; // a member's trailer, and the next BGZF header
142 if (::fstat(fd, &status) == 0 && status.st_size >= 4 &&
143 ::pread(fd, header, sizeof(header), 0) >= 4) {
144 auto const size = static_cast<uint64_t>(status.st_size);
145 probe.compressed = size;
146 auto name = path.filename().string();
147 std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) {
148 return static_cast<char>(std::tolower(c));
149 });
150 auto const fastq =
151 name.find(".fq") != std::string::npos || name.find(".fastq") != std::string::npos;
152 if (header[0] == '>') {
153 // Slot offsets count a plain file's bytes and its guard byte in 32 bits.
154 probe.plain = size < std::numeric_limits<uint32_t>::max();
155 probe.isize = static_cast<uint32_t>(probe.plain ? size : 0);
156 probe.device = probe.plain && !fastq;
157 } else if (size >= 26 && bgzf_block_size(header) != 0) {
158 // One read per block: its trailer, and the header of the block after it.
159 uint64_t at = 0, total = 0;
160 uint32_t crc = 0, block_size = bgzf_block_size(header);
161 bool tiled = false;
162 while (block_size >= 26 && block_size <= size - at) {
163 auto const next = at + block_size;
164 auto const wanted = next == size ? 8 : std::min<uint64_t>(26, size - next + 8);
165 if (::pread(fd, trailer, wanted, static_cast<off_t>(next - 8)) !=
166 static_cast<ssize_t>(wanted)) {
167 break;
168 }
169 uint32_t block_crc = 0, inflated = 0;
170 std::memcpy(&block_crc, trailer, 4);
171 std::memcpy(&inflated, trailer + 4, 4);
172 if (inflated != 0) {
173 crc = crc32_combine(crc, block_crc, inflated);
174 total += inflated;
175 ++probe.members;
176 }
177 if (next == size) {
178 tiled = true;
179 break;
180 }
181 block_size = wanted == 26 ? bgzf_block_size(trailer + 8) : 0;
182 at = next;
183 }
184 probe.bgzf = true;
185 probe.crc = crc;
186 probe.isize = static_cast<uint32_t>(total);
187 probe.device =
188 tiled && total != 0 && total < std::numeric_limits<uint32_t>::max() && !fastq;
189 } else if (size >= 18 && ::pread(fd, trailer, 8, static_cast<off_t>(size - 8)) == 8) {
190 std::memcpy(&probe.crc, trailer, 4);
191 std::memcpy(&probe.isize, trailer + 4, 4);
192 probe.members = 1;
193 probe.device = header[0] == 0x1f && header[1] == 0x8b && header[2] == 8 &&
194 (header[3] & 0xe4U) == 0 && probe.isize != 0 && !fastq;
195 }
196 }
197 ::close(fd);
198 return probe;
199}
constexpr uint32_t block_size
uint32_t bgzf_block_size(unsigned char const *header) noexcept
A BGZF block's total size from its 18-byte header, or 0 if it is not one: a gzip header with only FEX...
uint32_t crc32_combine(uint32_t a, uint32_t b, uint64_t length) noexcept
CRC32 of A followed by B, from CRC32(A), CRC32(B) and B's length, as zlib's crc32_combine: CRC32(A) t...
What a gzip file's header and trailer say, or that a file is plain FASTA, read without inflating it.
Here is the call graph for this function:

◆ propagate_error()

cuda::std::unexpected< Error > cuddl::detail::propagate_error ( const Error &  error)
inline

Copies error for propagation (avoids moving out of expected::error()).

Definition at line 169 of file error.hpp.

169 {
170 return cuda::std::unexpected<Error>(Error{error});
171}
Error payload carried in Result on failure.
Definition error.hpp:64

◆ reference_database_digest()

Result< uint64_t > cuddl::detail::reference_database_digest ( reference_database_file const &  db)
inline

Definition at line 42 of file reference_index_file.cuh.

42 {
44 CUDDL_TRY(hash.bytes("CUDDLDB-XXH64-v1", sizeof("CUDDLDB-XXH64-v1") - 1));
45 auto const metadata = db.metadata();
46 auto const& c = metadata.compatibility;
47 CUDDL_TRY(hash.value(c.kmer_length));
48 CUDDL_TRY(hash.value(c.bucket_count));
49 CUDDL_TRY(hash.value(c.indexed_bucket_count));
50 CUDDL_TRY(hash.value(c.score_encoder_identity));
51 CUDDL_TRY(hash.value(c.exponent_bits));
52 CUDDL_TRY(hash.value(c.mantissa_bits));
53 CUDDL_TRY(hash.value(c.hash_identity));
54 CUDDL_TRY(hash.value(c.hash_seed));
55 CUDDL_TRY(hash.value(c.canonicalisation_policy));
56 CUDDL_TRY(hash.value(c.blacklist_identity));
57 CUDDL_TRY(hash.value(c.blacklist_version));
58 CUDDL_TRY(hash.value(c.key_mask));
59 CUDDL_TRY(hash.value(metadata.reference_count));
60 CUDDL_TRY(hash.value(static_cast<uint64_t>(db.names().size())));
61 for (auto const& name : db.names()) {
62 CUDDL_TRY(hash.value(static_cast<uint64_t>(name.size())));
63 CUDDL_TRY(hash.bytes(name.data(), name.size()));
64 }
65 CUDDL_TRY(hash.words(db.rows()));
66 return hash.digest;
67}
Result< void > words(std::span< T const > items)
Result< void > bytes(void const *data, size_t size)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ refine_all_pairs_kernel()

template<size_t BucketCount, typename SearchResult , bool UpperTriangle, uint32_t QueryGroup>
__global__ void cuddl::detail::refine_all_pairs_kernel ( uint32_t const *  query_planes,
uint32_t  query_id_offset,
uint32_t  query_count,
uint32_t const *  reference_planes,
uint32_t  reference_count,
SearchResult *  results,
uint32_t *  cell_counter,
uint32_t const *  reference_mask 
)

Exactly compares the layout pairs selected by reference_mask, without match counts.

A null mask selects every reference.

Cells and query staging follow refine_batch_bitmap_kernel; warps claim all_pairs_word_parts parts of a 32-reference word at a time. A warp takes a claim's references two at a time and keeps both references' planes in registers, so each query plane group read from shared memory serves two pairs, and the two comparisons give every warp independent logic chains to interleave.

Definition at line 2316 of file kernels.cuh.

2325 {
2326 static_assert(match_filter_supported<BucketCount>);
2327 constexpr uint32_t warp_width = 32;
2328 constexpr uint32_t group = QueryGroup;
2329 static_assert(group >= 1U && group <= warp_width);
2330 constexpr uint32_t row_words = static_cast<uint32_t>(BucketCount / 2U);
2331 constexpr uint32_t lane_groups = static_cast<uint32_t>(BucketCount / warp_width / warp_width);
2332 constexpr uint32_t max_block_words = static_cast<uint32_t>(cuda::std::max<size_t>(
2333 1U, refine_block_row_bytes / (BucketCount * sizeof(uint16_t)) / warp_width
2334 ));
2335 extern __shared__ uint4 all_pairs_query_planes[];
2336 __shared__ uint32_t next_word;
2337 constexpr uint32_t part_width = warp_width / all_pairs_word_parts;
2338 __shared__ uint32_t claimed_cell;
2339
2340 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
2341 auto const words_per_query = candidate_bit_words(reference_count);
2342 auto const cells = make_refine_cells<UpperTriangle>(
2343 query_id_offset, query_count, group, words_per_query, max_block_words, 1U
2344 );
2345 auto const groups = cells.groups;
2346
2347 for (auto cell = next_cell(cell_counter, blockIdx.x - gridDim.x, claimed_cell);
2348 cell < cells.cell_count;
2349 cell = next_cell(cell_counter, cell, claimed_cell)) {
2350 auto const first_query = (cell % groups) * group;
2351 auto const group_size = cuda::std::min(group, query_count - first_query);
2352 auto const first_word = cells.first_word + (cell / groups) * cells.block_words;
2353 auto const last_word = cuda::std::min(first_word + cells.block_words, words_per_query);
2354 auto const* group_planes = reinterpret_cast<uint4 const*>(
2355 query_planes + static_cast<size_t>(first_query) * row_words
2356 );
2357
2358 __syncthreads();
2359 if (threadIdx.x == 0U) {
2361 }
2362 for (auto i = static_cast<uint32_t>(threadIdx.x); i < group_size * (row_words / 4U);
2363 i += all_pairs_block_size) {
2364 all_pairs_query_planes[i] = group_planes[i];
2365 }
2366 __syncthreads();
2367
2368 for (;;) {
2369 uint32_t word = 0U;
2370 if (lane == 0U) {
2371 word = atomicAdd(&next_word, 1U);
2372 }
2373 word = __shfl_sync(0xffffffffU, word, 0);
2374 auto const part = word % all_pairs_word_parts;
2376 if (word >= last_word) {
2377 break;
2378 }
2379 // Lane m holds query m's selected references in this part of the word.
2380 uint32_t bits = 0U;
2381 if (lane < group_size) {
2382 auto const first_reference = word * warp_width;
2383 auto const left = reference_count - first_reference;
2384 bits = left >= warp_width ? ~0U : (1U << left) - 1U;
2385 bits &= ((1U << part_width) - 1U) << (part * part_width);
2386 if constexpr (UpperTriangle) {
2387 auto const query_id = query_id_offset + first_query + lane;
2388 if (first_reference <= query_id) {
2389 auto const skipped = query_id - first_reference + 1U;
2390 bits &= skipped >= warp_width ? 0U : ~0U << skipped;
2391 }
2392 }
2393 if (reference_mask != nullptr) {
2395 }
2396 }
2397 auto remaining = __reduce_or_sync(0xffffffffU, bits);
2398 while (remaining != 0U) {
2399 auto const bit_a = static_cast<uint32_t>(__ffs(remaining) - 1);
2400 remaining &= remaining - 1U;
2401 auto const has_b = remaining != 0U;
2402 auto const bit_b = has_b ? static_cast<uint32_t>(__ffs(remaining) - 1) : bit_a;
2403 remaining &= remaining - 1U;
2404 auto const reference_a = word * warp_width + bit_a;
2405 auto const reference_b = word * warp_width + bit_b;
2406 auto const* planes_a = reinterpret_cast<uint4 const*>(
2407 reference_planes + static_cast<size_t>(reference_a) * row_words
2408 );
2409 auto const* planes_b = reinterpret_cast<uint4 const*>(
2410 reference_planes + static_cast<size_t>(reference_b) * row_words
2411 );
2412 uint32_t a[lane_groups][score_planes];
2413 uint32_t b[lane_groups][score_planes];
2414 uint32_t a_any[lane_groups];
2415 uint32_t b_any[lane_groups];
2416 uint32_t nonzero = ~0U;
2417 _Pragma("unroll")
2418 for (uint32_t j = 0; j < lane_groups; ++j) {
2419 load_plane_group(planes_a, j * warp_width + lane, a[j]);
2420 load_plane_group(planes_b, j * warp_width + lane, b[j]);
2421 a_any[j] = plane_any(a[j]);
2422 b_any[j] = plane_any(b[j]);
2423 nonzero &= a_any[j] & b_any[j];
2424 }
2425 // Selections are prefixes of the query group, and B's includes A's.
2426 auto const selected_a = __ballot_sync(0xffffffffU, (bits >> bit_a) & 1U);
2427 auto const selected_b = __ballot_sync(0xffffffffU, (bits >> bit_b) & 1U);
2428 auto const members = static_cast<uint32_t>(__popc(selected_b));
2429 // Lane m keeps member m's totals for both references.
2430 pairwise_counts mine_a{};
2431 pairwise_counts mine_b{};
2432 auto const compare = [&](auto full) {
2433 for (uint32_t m = 0U; m < members; ++m) {
2434 auto const* member =
2435 all_pairs_query_planes + static_cast<size_t>(m) * (row_words / 4U);
2436 plane_counts counts_a{};
2437 plane_counts counts_b{};
2438 _Pragma("unroll")
2439 for (uint32_t j = 0; j < lane_groups; ++j) {
2440 uint32_t q[score_planes];
2441 load_plane_group(member, j * warp_width + lane, q);
2442 compare_plane_group<false, decltype(full)::value>(
2443 q, a[j], a_any[j], 0U, 0U, 0U, counts_a
2444 );
2445 compare_plane_group<false, decltype(full)::value>(
2446 q, b[j], b_any[j], 0U, 0U, 0U, counts_b
2447 );
2448 }
2449 auto const total_a = counts_a.reduce<BucketCount, decltype(full)::value>();
2450 auto const total_b = counts_b.reduce<BucketCount, decltype(full)::value>();
2451 if (lane == m) {
2452 mine_a = total_a;
2453 mine_b = total_b;
2454 }
2455 }
2456 };
2457 if (__all_sync(0xffffffffU, nonzero == ~0U)) {
2458 compare(cuda::std::true_type{});
2459 } else {
2460 compare(cuda::std::false_type{});
2461 }
2462 // The slot implies the pair; only its counts are stored.
2463 if (((selected_a >> lane) & 1U) != 0U) {
2464 results[batch_result_slot<UpperTriangle>(
2465 first_query + lane, reference_a, query_id_offset, reference_count
2466 )] = SearchResult::pack(mine_a.lower, mine_a.equal, mine_a.higher);
2467 }
2468 if (has_b && ((selected_b >> lane) & 1U) != 0U) {
2469 results[batch_result_slot<UpperTriangle>(
2470 first_query + lane, reference_b, query_id_offset, reference_count
2471 )] = SearchResult::pack(mine_b.lower, mine_b.equal, mine_b.higher);
2472 }
2473 }
2474 }
2475 }
2476}
__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).
Definition kernels.cuh:1380
constexpr uint32_t all_pairs_word_parts
Parts a refine_all_pairs_kernel warp claims each 32-reference word in.
Definition kernels.cuh:2305
__device__ __forceinline__ uint32_t plane_any(uint32_t const (&planes)[score_planes]) noexcept
Buckets of a plane group holding a nonzero score.
Definition kernels.cuh:1421
__global__ uint32_t const uint32_t uint32_t const uint32_t const SearchResult * results
Definition kernels.cuh:1544
__device__ __forceinline__ uint32_t next_cell(uint32_t *cell_counter, uint32_t cell, uint32_t &claimed)
The cell a refinement-style block processes after cell: claimed from cell_counter,...
Definition kernels.cuh:1690
Here is the call graph for this function:

◆ refine_write_result()

template<typename SearchResult >
__device__ void cuddl::detail::refine_write_result ( uint32_t  index,
uint32_t  query_index,
uint32_t  reference_id,
uint32_t  query_id_offset,
uint32_t const *  match_counts,
uint32_t  pair_id,
pairwise_counts  local,
uint32_t  lane,
SearchResult *  results,
uint32_t *  result_match_counts 
)

Warp-reduces one refined candidate and writes its stable result slot.

Each counter reduces with one redux.sync instruction instead of a shuffle tree.

Definition at line 1290 of file kernels.cuh.

1301 {
1302 pairwise_counts const total{
1303 .lower = __reduce_add_sync(0xffffffffU, local.lower),
1304 .equal = __reduce_add_sync(0xffffffffU, local.equal),
1305 .higher = __reduce_add_sync(0xffffffffU, local.higher),
1306 .both_empty = __reduce_add_sync(0xffffffffU, local.both_empty),
1307 };
1308 if (lane == 0U) {
1309 results[index].query_id = query_id_offset + query_index;
1310 results[index].reference_id = reference_id;
1311 results[index].counts = total;
1312 if (result_match_counts != nullptr) {
1313 result_match_counts[index] = match_counts[pair_id];
1314 }
1315 }
1316}
Raw per-bucket classification counts for one sketch pair comparison.

◆ resolve_bases_packer()

bases_packer cuddl::detail::resolve_bases_packer ( )
inline

Selects the widest base packer the host supports.

Definition at line 739 of file fastx_sequence_file.hpp.

739 {
740 if (ablated("simd-parsing")) return {pack_bases_portable, 16};
741#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
742 if (__builtin_cpu_supports("avx512bw")) return {pack_bases_avx512, 64};
743 if (__builtin_cpu_supports("avx2")) return {pack_bases_avx2, 32};
744#endif
745#if defined(__aarch64__) || defined(_M_ARM64)
746 return {pack_bases_neon, 64};
747#else
748 return {pack_bases_portable, 16};
749#endif
750}
uint64_t pack_bases_portable(char const *bases, size_t count, uint32_t *words)
Here is the call graph for this function:

◆ resolve_fastx_line_end()

fastx_line_end_fn cuddl::detail::resolve_fastx_line_end ( )
inline

Selects the fastest line-end scanner the host supports.

Definition at line 178 of file fastx_sequence_file.hpp.

178 {
179 if (ablated("simd-parsing")) return fastx_line_end_scalar;
180#if defined(__ARM_FEATURE_SVE)
181 return fastx_line_end_sve;
182#elif defined(__aarch64__) || defined(_M_ARM64)
183 return fastx_line_end_neon;
184#elif (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)
185 if (__builtin_cpu_supports("avx512bw")) return fastx_line_end_avx512;
186 return __builtin_cpu_supports("avx2") ? fastx_line_end_avx2 : fastx_line_end_scalar;
187#else
189#endif
190}
size_t fastx_line_end_scalar(std::string_view data, size_t position)
Here is the call graph for this function:

◆ resolve_gzip_signature_scanner()

auto cuddl::detail::resolve_gzip_signature_scanner ( ) -> bool (*)(char const*, size_t)
inline

Selects the widest signature scanner the host supports.

Definition at line 305 of file device_gzip.cuh.

305 {
306 if (ablated("simd-parsing")) return has_gzip_signature_portable;
307#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
308 if (__builtin_cpu_supports("avx512bw")) return has_gzip_signature_avx512;
309 if (__builtin_cpu_supports("avx2")) return has_gzip_signature_avx2;
310#endif
311#if defined(__aarch64__) || defined(_M_ARM64)
312 return has_gzip_signature_neon;
313#endif
315}
bool has_gzip_signature_portable(char const *data, size_t size)
True when data holds what could start another gzip member: 1f 8b 08, then a flag byte with its reserv...
Here is the call graph for this function:

◆ restore()

template<typename Layout = default_register_layout>
__host__ __device__ constexpr uint64_t cuddl::detail::restore ( uint16_t  stored)
constexprnoexcept

Reconstructs the approximate original hash magnitude from a stored score.

Inverts the score encoding: recovers NLZ from the exponent field, uninverts the mantissa bits and prepends the implicit leading one, then shifts the result back into 64-bit space.

Definition at line 68 of file register.cuh.

68 {
69 constexpr auto layout_mantissa_mask = (1U << Layout::mantissa_bits) - 1U;
70 constexpr auto layout_restore_shift_base = 63U - Layout::mantissa_bits;
71 auto const nlz = static_cast<uint32_t>(stored >> Layout::mantissa_bits);
72 auto const lowbits = static_cast<uint32_t>((~stored) & layout_mantissa_mask);
73 auto const mantissa = (1U << Layout::mantissa_bits) | lowbits;
74 auto const shift = layout_restore_shift_base - nlz;
75 return static_cast<uint64_t>(mantissa) << shift;
76}

◆ restore_midpoint()

template<typename Layout = default_register_layout>
__host__ __device__ constexpr uint64_t cuddl::detail::restore_midpoint ( uint16_t  stored)
constexprnoexcept

Reconstructs the interval midpoint for restore.

restore returns the lower bound of the hash interval represented by stored. The actual hash is uniformly distributed across that interval, so the midpoint is the minimum-variance scalar summary and reduces the small positive bias that lower-bound restoration introduces into hash-magnitude cardinality sums.

Definition at line 87 of file register.cuh.

87 {
88 constexpr auto layout_mantissa_mask = (1U << Layout::mantissa_bits) - 1U;
89 constexpr auto layout_restore_shift_base = 63U - Layout::mantissa_bits;
90 auto const nlz = static_cast<uint32_t>(stored >> Layout::mantissa_bits);
91 auto const lowbits = static_cast<uint32_t>((~stored) & layout_mantissa_mask);
92 auto const mantissa = (1U << Layout::mantissa_bits) | lowbits;
93 auto const shift = layout_restore_shift_base - nlz;
94 auto const lower = static_cast<uint64_t>(mantissa) << shift;
95 // `(1 << shift) >> 1` is 2^(shift-1) for ordinary tiers and exactly zero for the
96 // clamped top tier (`shift == 0`), so the midpoint needs no branch.
97 return lower + ((1ULL << shift) >> 1U);
98}

◆ reverse_complement()

constexpr uint64_t cuddl::detail::reverse_complement ( uint64_t  packed,
uint32_t  k 
)
inlineconstexprnoexcept

Reverse-complements a packed k-mer (2 bits per base) with bit-parallel reversal.

Reverses the order of the 2-bit base digits with mask-swap rounds, shifts the reversed 2k bits down from the high end, then complements every base (^0b10). Byte-identical to the scalar loop formulation for every k <= 32; longer k-mers fall back to the scalar loop. The __host__ __device__ annotation only exists under the CUDA compiler so plain host translation units see a normal inline function, as with encode_base.

Definition at line 42 of file fasta_parser.hpp.

42 {
43 if (k > 32) {
44 uint64_t result = 0;
45 for (uint32_t base = 0; base < k; ++base) {
46 result = (result << 2U) | static_cast<uint64_t>(static_cast<uint8_t>(packed & 3U) ^ 2U);
47 packed >>= 2U;
48 }
49 return result;
50 }
51 uint64_t x = packed;
52 x = ((x & 0xCCCCCCCCCCCCCCCCULL) >> 2U) | ((x & 0x3333333333333333ULL) << 2U);
53 x = ((x & 0xF0F0F0F0F0F0F0F0ULL) >> 4U) | ((x & 0x0F0F0F0F0F0F0F0FULL) << 4U);
54 x = ((x & 0xFF00FF00FF00FF00ULL) >> 8U) | ((x & 0x00FF00FF00FF00FFULL) << 8U);
55 x = ((x & 0xFFFF0000FFFF0000ULL) >> 16U) | ((x & 0x0000FFFF0000FFFFULL) << 16U);
56 x = (x >> 32U) | (x << 32U);
57 x >>= (64U - 2U * k);
58 auto const mask = (k == 32) ? ~uint64_t{0} : ((1ULL << (2U * k)) - 1ULL);
59 return (x ^ 0xAAAAAAAAAAAAAAAAULL) & mask;
60}
Here is the caller graph for this function:

◆ rotl64()

constexpr __host__ __device__ __forceinline__ uint64_t cuddl::detail::rotl64 ( uint64_t  x,
int8_t  r 
)
constexpr

Rotates x left by r bits.

Definition at line 40 of file hash.cuh.

40 {
41 return cuda::std::rotl(x, static_cast<int>(r));
42}
Here is the caller graph for this function:

◆ row_type()

Result< MPI_Datatype > cuddl::detail::row_type ( uint32_t  buckets)
inline

One row of buckets scores as a single MPI element, so counts of rows stay within int.

Definition at line 264 of file distributed.cuh.

264 {
265 MPI_Datatype row = MPI_DATATYPE_NULL;
267 MPI_Type_contiguous(static_cast<int>(buckets), MPI_UINT16_T, &row), "MPI_Type_contiguous"
268 ));
269 CUDDL_TRY(mpi_try(MPI_Type_commit(&row), "MPI_Type_commit"));
270 return row;
271}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ score()

template<typename Layout = default_register_layout>
__host__ __device__ uint16_t cuddl::detail::score ( uint64_t  hash)
inlinenoexcept

Computes the 16-bit DDL rarity score for hash.

The exponent field stores the number of leading zeros (NLZ); the mantissa field stores the mantissa_bits bits immediately following the leading one, bitwise inverted. A larger score always encodes a rarer hash. Zero is reserved for the empty register, so a hash whose raw exponent/mantissa encoding is zero collapses to one.

Definition at line 43 of file register.cuh.

43 {
44 constexpr auto layout_mantissa_mask = (1U << Layout::mantissa_bits) - 1U;
45 constexpr auto layout_restore_shift_base = 63U - Layout::mantissa_bits;
46 constexpr auto layout_max_nlz =
47 cuda::std::min(layout_restore_shift_base, (1U << Layout::exponent_bits) - 1U);
48#ifdef __CUDA_ARCH__
49 auto const nlz = static_cast<uint32_t>(__clzll(hash | 1ULL));
50#else
51 auto const nlz = static_cast<uint32_t>(__builtin_clzll(hash | 1ULL));
52#endif
53 auto const capped = nlz > layout_max_nlz ? layout_max_nlz : nlz;
54 auto const shift = layout_restore_shift_base - capped;
55 auto const mantissa = static_cast<uint32_t>((hash >> shift) & layout_mantissa_mask);
56 auto const inverted = static_cast<uint32_t>(mantissa ^ layout_mantissa_mask);
57 auto const raw = static_cast<uint16_t>((capped << Layout::mantissa_bits) | inverted);
58 return raw == 0U ? static_cast<uint16_t>(1U) : raw;
59}
Here is the caller graph for this function:

◆ score_plane_index()

__host__ __device__ constexpr uint32_t cuddl::detail::score_plane_index ( uint32_t  group,
uint32_t  plane 
)
constexprnoexcept

Position of plane plane of 32-bucket group group within a row's bit-planes.

A row of BucketCount 16-bit scores becomes BucketCount / 2 plane words: word p of group g holds bit p of the group's 32 scores. Lane g % 32 of a warp owns group g, and its 16 plane words form four consecutive uint4s at lane-contiguous positions, so a warp reads a row with coalesced 128-bit loads and shared copies with conflict-free 128-bit loads.

Definition at line 1334 of file kernels.cuh.

1334 {
1335 return (((group / 32U) * 4U + plane / 4U) * 32U + group % 32U) * 4U + plane % 4U;
1336}
Here is the caller graph for this function:

◆ share_listing()

Result< std::vector< std::filesystem::path > > cuddl::detail::share_listing ( std::filesystem::path const &  input,
int  rank,
MPI_Comm  comm 
)
inline

Rank 0's listing of input on every rank.

Definition at line 89 of file distributed.cuh.

89 {
90 std::string joined;
91 auto listed = [&]() -> Result<void> {
92 if (rank != 0) return Ok();
93 auto const paths = CUDDL_TRY(list_genomes(input));
94 for (auto const& path : paths) joined += path.string() + '\n';
95 return Ok();
96 }();
97 CUDDL_TRY(agree(std::move(listed), comm));
98 uint64_t length = joined.size();
99 CUDDL_TRY(mpi_try(MPI_Bcast(&length, 1, MPI_UINT64_T, 0, comm), "MPI_Bcast"));
100 joined.resize(length);
101 // MPI counts are int: send the listing in chunks below 2 GiB.
102 constexpr uint64_t chunk = uint64_t{1} << 30U;
103 for (uint64_t offset = 0; offset < length; offset += chunk) {
104 auto const count = static_cast<int>(std::min(chunk, length - offset));
105 CUDDL_TRY(
106 mpi_try(MPI_Bcast(joined.data() + offset, count, MPI_CHAR, 0, comm), "MPI_Bcast")
107 );
108 }
109 std::vector<std::filesystem::path> paths;
110 for (size_t begin = 0; begin < joined.size();) {
111 auto const end = joined.find('\n', begin);
112 paths.emplace_back(joined.substr(begin, end - begin));
113 begin = end + 1;
114 }
115 return paths;
116}
Result< std::vector< std::filesystem::path > > list_genomes(std::filesystem::path const &input)
Paths from a directory (see genome_paths) or from a text file with one path per line,...
Here is the call graph for this function:
Here is the caller graph for this function:

◆ shared_search()

template<typename Search >
Result< void > cuddl::detail::shared_search ( MPI_Comm  comm,
Search &&  search 
)

Runs search with a replica_search that shares tiles across the ranks of comm.

Definition at line 449 of file distributed.cuh.

449 {
450 int rank = 0;
451 int size = 1;
452 CUDDL_TRY(mpi_try(MPI_Comm_rank(comm, &rank), "MPI_Comm_rank"));
453 CUDDL_TRY(mpi_try(MPI_Comm_size(comm, &size), "MPI_Comm_size"));
454 auto counter = CUDDL_TRY(shared_tile_counter::make(comm));
455 return search(
457 .shard = {.index = static_cast<uint32_t>(rank), .count = static_cast<uint32_t>(size)},
458 .next_tile = detail::ablated("tile-queue") ? std::function<uint64_t()>{}
459 : [&counter] { return counter(); },
460 }
461 );
462}
How a replicated_database search splits and delivers its tiles.
all_to_all_shard shard
This process's part of the pairs, when several processes share the search.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ slice_begin()

size_t cuddl::detail::slice_begin ( std::span< uint64_t const >  bytes,
uint64_t  total,
int  rank,
int  size 
)
inline

First genome of rank rank's slice: contiguous slices of about equal bytes.

Definition at line 120 of file distributed.cuh.

120 {
121 auto const target = static_cast<uint64_t>(static_cast<double>(total) * rank / size);
122 uint64_t sum = 0;
123 size_t i = 0;
124 while (i < bytes.size() && sum + bytes[i] / 2 < target) sum += bytes[i++];
125 return i;
126}
Here is the caller graph for this function:

◆ slice_counts()

Result< std::vector< uint32_t > > cuddl::detail::slice_counts ( reference_database_file const &  mine,
genome_slice const &  slice,
MPI_Comm  comm 
)
inline

Every rank's genome count, after checking collectively that mine holds the sketches of this rank's slice and that every rank built with rank 0's settings.

Definition at line 235 of file distributed.cuh.

235 {
236 auto const metadata = mine.metadata();
237 auto reference = metadata.compatibility;
238 CUDDL_TRY(mpi_try(MPI_Bcast(&reference, sizeof(reference), MPI_BYTE, 0, comm), "MPI_Bcast"));
239 auto local = [&]() -> Result<void> {
240 if (metadata.reference_count != slice.last - slice.first) {
241 return Err(Error::invalid_argument("sketches do not match this rank's genome slice"));
242 }
243 if (!(reference == metadata.compatibility)) {
244 return Err(Error::invalid_argument("ranks sketched with different settings"));
245 }
246 return Ok();
247 }();
248 CUDDL_TRY(agree(std::move(local), comm));
249 int size = 1;
250 CUDDL_TRY(mpi_try(MPI_Comm_size(comm, &size), "MPI_Comm_size"));
251 uint32_t const count = metadata.reference_count;
252 std::vector<uint32_t> counts(size);
254 MPI_Allgather(&count, 1, MPI_UINT32_T, counts.data(), 1, MPI_UINT32_T, comm),
255 "MPI_Allgather"
256 ));
257 if (std::accumulate(counts.begin(), counts.end(), uint64_t{0}) != slice.paths.size()) {
258 return Err(Error::invalid_argument("ranks hold slices of different genome collections"));
259 }
260 return counts;
261}
Result< void > agree(Result< void > local, MPI_Comm comm)
Returns local on the rank that failed, and on every other rank an error naming the lowest failing ran...
Here is the call graph for this function:
Here is the caller graph for this function:

◆ smaller_index_storage()

constexpr index_storage cuddl::detail::smaller_index_storage ( uint32_t  reference_count,
score_compatibility const &  compatibility 
)
inlineconstexprnoexcept

Storage needing fewer device bytes for reference_count references; dense on ties.

Both store one 32-bit posting per indexed row, so only the per-storage extras are compared: dense offsets per key cell against sparse 16-bit keys and their derived key directory.

Definition at line 936 of file reference_database.cuh.

936 {
937 auto const dense = (indexed_cell_count(compatibility) + 1U) * sizeof(uint32_t);
938 auto const sparse = indexed_posting_count(reference_count, compatibility) * sizeof(uint16_t) +
939 static_cast<uint64_t>(sparse_directory_entries(reference_count)) *
940 compatibility.indexed_bucket_count * sizeof(uint32_t);
941 return sparse < dense ? index_storage::sparse : index_storage::dense;
942}
constexpr uint64_t indexed_posting_count(uint32_t reference_count, score_compatibility const &compatibility) noexcept
constexpr uint64_t indexed_cell_count(score_compatibility const &compatibility) noexcept
Here is the call graph for this function:

◆ sparse_directory_entries()

__host__ __device__ constexpr uint32_t cuddl::detail::sparse_directory_entries ( uint32_t  reference_count)
constexprnoexcept

Bounds per bucket in a sparse key directory.

Definition at line 429 of file kernels.cuh.

429 {
430 return (1U << (16U - sparse_directory_shift(reference_count))) + 1U;
431}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ sparse_directory_shift()

__host__ __device__ constexpr uint32_t cuddl::detail::sparse_directory_shift ( uint32_t  reference_count)
constexprnoexcept

Low key bits a sparse key directory cell spans for reference_count references.

Cells average about eight keys per bucket for uniformly spread keys, so a lookup reads the cell's two bounds and then searches within about one memory sector.

Definition at line 420 of file kernels.cuh.

420 {
421 uint32_t cell_bits = 0U;
422 while (cell_bits < 16U && (uint64_t{1} << (cell_bits + 1U)) * 8U <= reference_count) {
423 ++cell_bits;
424 }
425 return 16U - cell_bits;
426}
Here is the caller graph for this function:

◆ splitmix64()

__host__ __device__ constexpr uint64_t cuddl::detail::splitmix64 ( uint64_t  value)
constexprnoexcept

SplitMix64 finalizer, applied to a packed k-mer XOR the domain seed.

Definition at line 9 of file hash.cuh.

9 {
10 value ^= value >> 30;
11 value *= 0xbf58476d1ce4e5b9ULL;
12 value ^= value >> 27;
13 value *= 0x94d049bb133111ebULL;
14 return value ^ (value >> 31);
15}
Here is the caller graph for this function:

◆ stage_groups()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout, typename Fill , typename Sink >
Result< void > cuddl::detail::stage_groups ( size_t  genomes,
uint64_t  staged_ceiling,
std::optional< size_t >  staging_bytes,
cuda::stream_ref  stream,
reference_build_statistics *  statistics,
Fill &&  fill,
Sink &&  sink,
bool  in_place = false,
cuda::std::optional< blacklist_view >  blacklist = cuda::std::nullopt 
)

Runs the staging loop over a collection, filling one group at a time.

fill receives the stager, a group's first genome and its size, and stages that whole group. Genomes are numbered within their group, so a fill stages genome genome - base. sink receives each finished group's device rows with its first genome and size, and must enqueue every read of them on stream: the next group reuses the rows.

Definition at line 1111 of file database_staging.cuh.

1121 {
1122 auto const plan =
1123 CUDDL_TRY((plan_staging<K, BucketCount>(genomes, staged_ceiling, staging_bytes, stream)));
1124 database_stager<K, BucketCount, Layout> stager(stream, plan, statistics, in_place, blacklist);
1125 size_t base = 0;
1126 while (base < genomes) {
1127 auto const count = std::min(plan.group, genomes - base);
1128 CUDDL_TRY(stager.begin_group(count));
1129 CUDDL_TRY(fill(stager, base, count));
1130 auto const rows = CUDDL_TRY(stager.end_group(count));
1131 CUDDL_TRY(sink(rows, base, count));
1132 base += count;
1133 }
1134 return stager.finish();
1135}
Encodes genome records into register rows through a device arena.
__global__ size_t uint16_t * rows
Definition kernels.cuh:1499
Here is the call graph for this function:

◆ stage_paths()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout, typename Sink >
Result< void > cuddl::detail::stage_paths ( std::span< std::filesystem::path const >  paths,
cuda::stream_ref  stream,
std::optional< size_t >  staging_bytes,
unsigned  parser_workers,
transfer_mode  transfer,
decompression_backend  decompression,
bool  direct_reads,
reference_build_statistics *  statistics,
Sink &&  sink,
cuda::std::optional< blacklist_view >  blacklist = cuda::std::nullopt 
)

Stages a path collection, handing each group's device rows to sink.

parser_workers is a value, not a sentinel: the build clamps it to what the inputs and the hardware allow, so asking for the default is asking for the default.

The arena ceiling is the decompressed size of every input: a bound from compressed sizes alone overestimates a corpus several times over, and an arena past what the corpus can hold is memory the device never needs.

Definition at line 1612 of file database_staging.cuh.

1623 {
1624 // Loaders and inflation reuse pinned buffers through the build; none outlive it.
1625 pinned_pool_trim const release_pinned{stream.device(), pinned_placement::host};
1626 if (parser_workers == 0) {
1627 return Err(Error::invalid_argument("a path build needs at least one loader"));
1628 }
1629#if !CUDDL_HAS_NVCOMP
1630 if (decompression == decompression_backend::gpu ||
1631 decompression == decompression_backend::gpu_cores ||
1632 decompression == decompression_backend::hybrid) {
1633 return Err(
1634 Error::invalid_argument("requested decompression backend requires nvCOMP support")
1635 );
1636 }
1637 decompression = decompression_backend::cpu;
1638#endif
1639 if (transfer == transfer_mode::in_place && !device_reads_pageable_memory(stream.device())) {
1640 return Err(
1641 Error::invalid_argument(
1642 "in-place staging needs a device that reads pageable host memory"
1643 )
1644 );
1645 }
1646 if (paths.empty()) return Ok();
1647 // A coherent GPU reads the loaders' buffers in place, so CPU inflation feeds it best.
1648 // Elsewhere nvCOMP wins: 100,000 genomes on an RTX 5090 with 120 loaders built in 7.4 s with
1649 // gpu and 10.2 s with cpu, and the CPU-inflating backends kept the GPU 60% idle.
1650 if (decompression == decompression_backend::automatic) {
1651 if (device_reads_pageable_memory(stream.device())) {
1652 decompression = path_loaders::worker_count(paths.size(), parser_workers) > 1
1653 ? decompression_backend::hybrid
1654 : decompression_backend::cpu;
1655 } else {
1656 decompression = decompression_backend::gpu;
1657 }
1658 }
1659#if CUDDL_HAS_NVCOMP
1660 if (decompression == decompression_backend::hybrid) {
1661 return stage_paths_normalising<K, BucketCount, Layout>(
1662 paths,
1663 stream,
1664 staging_bytes,
1665 parser_workers,
1666 transfer,
1667 direct_reads,
1668 statistics,
1669 std::forward<Sink>(sink),
1670 blacklist
1671 );
1672 }
1673 if (decompression != decompression_backend::cpu) {
1674 return stage_paths_inflating<K, BucketCount, Layout>(
1675 paths,
1676 stream,
1677 staging_bytes,
1678 parser_workers,
1679 transfer,
1680 direct_reads,
1681 decompression == decompression_backend::gpu,
1682 statistics,
1683 std::forward<Sink>(sink),
1684 blacklist
1685 );
1686 }
1687#endif
1688 auto const in_place = stages_in_place(transfer, stream.device());
1689 auto const page_locked = pages_locked(transfer, stream.device());
1690 // Copied bases cross the bus, and packing them first moves a quarter of the bytes. Unpacked,
1691 // an A100 took 2.0-2.2 s for 10,000 genomes from 32 to 112 loaders, held by the copies;
1692 // packed, 1.8 s with 32 and 1.3 s with 64 or more. Packing costs the loaders time, so with
1693 // 16 it took 3.4 s against 2.8 s. A device that reads the bases in place gains nothing from
1694 // it: 16 loaders on a GH200 took 3.0 s packed against 2.7 s without.
1695 path_loaders loaders(
1696 stream,
1697 paths,
1698 parser_workers,
1699 page_locked,
1700 in_place,
1701 in_place || ablated("base-packing") ? 0 : K,
1702 direct_reads
1703 );
1704 // The corpus size only bounds a default arena, and only below its ceiling, so probing stops
1705 // there: a corpus of any size then costs at most a few dozen file opens here.
1706 uint64_t staged_ceiling = 0;
1707 for (auto const& path : paths) {
1708 if (staging_bytes.has_value() || staged_ceiling >= default_arena_ceiling) break;
1709 auto const decompressed = gzip_decompressed_size(path.string());
1710 if (decompressed != 0) {
1711 staged_ceiling += decompressed;
1712 continue;
1713 }
1714 std::error_code error;
1715 auto const size = std::filesystem::file_size(path, error);
1716 if (!error) staged_ceiling += size;
1717 }
1718 CUDDL_TRY((stage_groups<K, BucketCount, Layout>(
1719 paths.size(),
1720 staged_ceiling,
1721 staging_bytes,
1722 stream,
1723 statistics,
1724 [&](database_stager<K, BucketCount, Layout>& stager, size_t base, size_t count)
1725 -> Result<void> {
1726 for (size_t id = base; id < base + count; ++id) {
1727 auto sequence = CUDDL_TRY(loaders.take(paths, id));
1728 // Read the loaded file's parts before the move below: argument order is
1729 // unspecified, so a moved-from Result must not be dereferenced.
1730 auto const* const pinned_base =
1731 page_locked ? sequence->decompressed_target : nullptr;
1732 auto const pinned_size = page_locked ? sequence->decompressed_size : 0;
1733 auto const& extents = sequence->extents;
1734 if (sequence->words != nullptr) {
1735 CUDDL_TRY(stager.add_packed_genome(
1736 id - base, std::move(sequence), pinned_base, pinned_size
1737 ));
1738 continue;
1739 }
1740 CUDDL_TRY(stager.add_genome(
1741 id - base, extents, pinned_base, pinned_size, std::move(sequence)
1742 ));
1743 }
1744 return Ok();
1745 },
1746 sink,
1747 in_place,
1748 blacklist
1749 )));
1750 if (statistics != nullptr) {
1751 statistics->pinned_buffers = loaders.page_locked_buffers();
1752 statistics->workers = static_cast<unsigned>(loaders.workers());
1753 statistics->in_place = in_place;
1754 }
1755 loaders.reset();
1756 return Ok();
1757}
Returns the free pages of a device_pinned_pool to the OS when it leaves scope.
bool pages_locked(transfer_mode mode, cuda::device_ref device) noexcept
Whether a build in mode decompresses into page-locked buffers.
bool stages_in_place(transfer_mode mode, cuda::device_ref device) noexcept
Whether a build in mode stages the caller's bytes in place.
uint64_t gzip_decompressed_size(std::string const &path)
Uncompressed size the gzip trailer advertises, or 0 when it cannot be read.
Here is the call graph for this function:

◆ stage_sequences()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout, typename Sink >
Result< void > cuddl::detail::stage_sequences ( std::span< sequence_genome const >  genomes,
cuda::stream_ref  stream,
std::optional< size_t >  staging_bytes,
reference_build_statistics *  statistics,
Sink &&  sink,
cuda::std::optional< blacklist_view >  blacklist = cuda::std::nullopt 
)

Stages bases the caller already holds, handing each group's device rows to sink.

Definition at line 1761 of file database_staging.cuh.

1768 {
1769 if (genomes.empty()) return Ok();
1770 pinned_pool_trim const release_pinned{stream.device(), pinned_placement::host};
1771 // The caller knows exactly what the corpus holds, so the ceiling is the sum of its bases
1772 // rather than an estimate read out of file headers.
1773 uint64_t staged_ceiling = 0;
1774 for (auto const& genome : genomes) {
1775 for (auto const& record : genome.records) staged_ceiling += record.bases.size();
1776 }
1777 std::vector<fastx_sequence_extent> records;
1778 return stage_groups<K, BucketCount, Layout>(
1779 genomes.size(),
1780 staged_ceiling,
1781 staging_bytes,
1782 stream,
1783 statistics,
1784 [&](database_stager<K, BucketCount, Layout>& stager,
1785 size_t base,
1786 size_t count) -> Result<void> {
1787 for (size_t id = base; id < base + count; ++id) {
1788 auto const& genome = genomes[id];
1789 records.clear();
1790 records.reserve(genome.records.size());
1791 for (auto const& record : genome.records) {
1792 if (record.bases.empty()) continue;
1793 records.push_back(
1794 {record.bases.data(), record.bases.data() + record.bases.size()}
1795 );
1796 }
1797 CUDDL_TRY(stager.add_genome(id - base, records));
1798 }
1799 return Ok();
1800 },
1801 sink,
1802 false,
1803 blacklist
1804 );
1805}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ stages_in_place()

bool cuddl::detail::stages_in_place ( transfer_mode  mode,
cuda::device_ref  device 
)
inlinenoexcept

Whether a build in mode stages the caller's bytes in place.

Automatic transfer reads CPU-decoded buffers in place on coherent devices.

Definition at line 90 of file database_staging.cuh.

90 {
91 return mode == transfer_mode::in_place ||
92 (mode == transfer_mode::automatic && device_reads_pageable_memory(device));
93}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ sum_posting_bitmaps()

template<uint32_t CounterBits>
__device__ uint32_t cuddl::detail::sum_posting_bitmaps ( uint32_t const *  bitmaps,
uint32_t const *  starts,
uint32_t  count,
uint32_t  word,
uint32_t  minimum_matches,
uint32_t  passed,
uint32_t(&)  counters[CounterBits] 
)

Sums one query's posting bitmaps over one word of references in bit-sliced counters.

Independent bitmap loads overlap in batches. Returns passed with every reference whose count reaches minimum_matches added; summing stops once all 32 have passed, so a passed reference's count is unused and may wrap.

Definition at line 844 of file kernels.cuh.

852 {
853 constexpr uint32_t batch = 8U;
854 for (uint32_t i = 0U; i < count && passed != ~0U; i += batch) {
855 // Keep independent bitmap reads in flight before consuming their values.
856 uint32_t loaded[batch]{};
857 _Pragma("unroll")
858 for (uint32_t j = 0U; j < batch; ++j) {
859 if (i + j < count) {
860 loaded[j] = bitmaps[starts[i + j] + word];
861 }
862 }
863 // Reduce eight bitmaps to four input count planes.
864 auto const sum0 = loaded[0] ^ loaded[1] ^ loaded[2];
865 auto const carry0 = (loaded[0] & loaded[1]) | ((loaded[0] | loaded[1]) & loaded[2]);
866 auto const sum1 = loaded[3] ^ loaded[4] ^ loaded[5];
867 auto const carry1 = (loaded[3] & loaded[4]) | ((loaded[3] | loaded[4]) & loaded[5]);
868 auto const ones0 = sum0 ^ sum1 ^ loaded[6];
869 auto const carry2 = (sum0 & sum1) | ((sum0 | sum1) & loaded[6]);
870 auto const twos0 = carry0 ^ carry1 ^ carry2;
871 auto const fours0 = (carry0 & carry1) | ((carry0 | carry1) & carry2);
872 auto const carry3 = ones0 & loaded[7];
873 auto const carry4 = twos0 & carry3;
874 uint32_t const additions[]{
875 ones0 ^ loaded[7], twos0 ^ carry3, fours0 ^ carry4, fours0 & carry4
876 };
877 uint32_t carry = 0U;
878 uint32_t at_least = ~0U;
879 _Pragma("unroll")
880 for (uint32_t bit = 0U; bit < CounterBits; ++bit) {
881 auto const extra = bit < 4U ? additions[bit] : 0U;
882 auto const previous = counters[bit];
883 counters[bit] = previous ^ carry ^ extra;
884 carry = (previous & carry) | ((previous | carry) & extra);
885 auto const key = 0U - ((minimum_matches >> bit) & 1U);
886 at_least = (counters[bit] & ~key) | ((counters[bit] | ~key) & at_least);
887 }
888 // Overflow is above every threshold representable by these counter planes.
889 passed |= at_least | carry;
890 _Pragma("unroll")
891 for (uint32_t bit = CounterBits; bit < 4U; ++bit) {
892 passed |= additions[bit];
893 }
894 }
895 return passed;
896}
uint32_t carry
Definition kernels.cuh:547
Here is the call graph for this function:
Here is the caller graph for this function:

◆ top_k_heap_words()

constexpr size_t cuddl::detail::top_k_heap_words ( uint32_t  k)
constexprnoexcept

Words of shared memory one heap takes: k keys, then their k packed counts.

Definition at line 56 of file top_k.cuh.

56 {
57 return size_t{k} * 2U;
58}
Here is the caller graph for this function:

◆ top_k_key()

__host__ __device__ constexpr uint64_t cuddl::detail::top_k_key ( pairwise_counts const &  counts,
uint32_t  id 
)
constexprnoexcept

Ranking key of a neighbour: its weighted k-mer identity above its complemented ID.

Keys are unique, a larger key ranks higher, and equal identities rank the smaller ID higher, so any selection of the k largest keys is the same set. Zero means no candidate; a real key is never zero because IDs stay below UINT32_MAX.

Definition at line 29 of file top_k.cuh.

29 {
30 auto const identity = cuda::std::bit_cast<uint32_t>(weighted_kmer_identity(counts));
31 return (uint64_t{identity} << 32U) | static_cast<uint32_t>(~id);
32}
__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...
Here is the call graph for this function:
Here is the caller graph for this function:

◆ top_k_nth_bit()

__device__ uint32_t cuddl::detail::top_k_nth_bit ( uint32_t  mask,
uint32_t  n 
)
inline

Position of set bit n, counting from zero, of mask, which has more than n set bits.

Definition at line 114 of file top_k.cuh.

114 {
115 uint32_t position = 0U;
116 asm("fns.b32 %0, %1, 0, %2;" : "=r"(position) : "r"(mask), "r"(static_cast<int>(n + 1U)));
117 return position;
118}
Here is the caller graph for this function:

◆ top_k_replace_root()

__device__ void cuddl::detail::top_k_replace_root ( uint64_t *  heap,
uint32_t  k,
uint64_t  key,
uint64_t  value 
)
inline

Replaces the root of a min-heap of k keys at heap, whose values follow the keys, with key and value, and sifts it down.

The caller owns the heap and has checked that key beats the root.

Definition at line 66 of file top_k.cuh.

66 {
67 auto* const values = heap + k;
68 uint32_t at = 0U;
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;
73 heap[at] = child_key;
74 values[at] = values[child];
75 at = child;
76 }
77 heap[at] = key;
78 values[at] = value;
79}
auto *const heap
Definition top_k.cuh:200
Here is the caller graph for this function:

◆ top_k_stored()

template<bool UpperTriangle>
__device__ packed_pairwise_counts cuddl::detail::top_k_stored ( batch_result_tile const &  tile,
uint32_t  query,
uint32_t  reference 
)
inline

The stored result of tile row query and reference, which must pass.

Definition at line 106 of file top_k.cuh.

106 {
107 auto const slot = batch_result_slot<UpperTriangle>(
108 query, reference, tile.first_query_id, tile.reference_count
109 );
110 return packed_pairwise_counts{__ldg(&tile.results[slot].bits)};
111}

◆ top_k_warp_copy()

template<bool ToShared>
__device__ void cuddl::detail::top_k_warp_copy ( top_k_slots const &  slots,
size_t  row,
uint64_t *  heap 
)
inline

Copies the k entries of global heap row into, or for ToShared false out of, the shared heap heap with the warp's lanes.

Definition at line 177 of file top_k.cuh.

177 {
178 auto const k = slots.k;
179 for (auto i = static_cast<uint32_t>(threadIdx.x) % 32U; i < k; i += 32U) {
180 if constexpr (ToShared) {
181 heap[i] = slots.keys[row * k + i];
182 heap[k + i] = slots.counts[row * k + i];
183 } else {
184 slots.keys[row * k + i] = heap[i];
185 slots.counts[row * k + i] = heap[k + i];
186 }
187 }
188 __syncwarp();
189}
__global__ top_k_slots slots
Definition top_k.cuh:195

◆ top_k_warp_copy< false >()

cuddl::detail::top_k_warp_copy< false > ( slots  ,
row  ,
heap   
)

◆ top_k_warp_copy< true >()

cuddl::detail::top_k_warp_copy< true > ( slots  ,
row  ,
heap   
)

◆ top_k_warp_merge() [1/2]

cuddl::detail::top_k_warp_merge ( heap  ,
slots.  k,
words -   cuda::std::minskipped, words,
[&] (uint32_t word) { return top_k_word_bits< Thresholded, UpperTriangle >(tile, query, skipped+word, words);}  ,
[&] (uint32_t candidate, uint64_t &key, uint64_t &value) { auto const reference=skipped *32U+candidate;auto const stored=top_k_stored< UpperTriangle >(tile, query, reference);key=top_k_key(stored.unpack(tile.bucket_count), reference);value=stored.bits;}   
)

◆ top_k_warp_merge() [2/2]

template<typename Bits , typename Entry >
__device__ void cuddl::detail::top_k_warp_merge ( uint64_t *  heap,
uint32_t  k,
uint32_t  words,
Bits  bits_of,
Entry  entry_of 
)
inline

One warp merges the candidates of words bitmap words into the heap of k entries at heap, in shared memory, which only this warp updates, so it needs no lock.

bits_of gives word w's candidates and entry_of candidate w * 32 + bit's key and value. The warp takes 32 words at a time and hands their set bits out 32 per step, one per lane in order, so every step offers 32 candidates however the words are filled. Lanes test their candidates against the root; the warp then inserts the survivors one at a time.

Definition at line 131 of file top_k.cuh.

131 {
132 auto const lane = static_cast<uint32_t>(threadIdx.x) % 32U;
133 for (uint32_t base = 0U; base < words; base += 32U) {
134 auto const bits = base + lane < words ? bits_of(base + lane) : 0U;
135 // Inclusive prefix of the lanes' set bits, so candidate j is in the first lane whose
136 // prefix exceeds j.
137 auto const count = static_cast<uint32_t>(cuda::std::popcount(bits));
138 auto prefix = count;
139 _Pragma("unroll")
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;
143 }
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;
147 // Every lane takes part in the search's shuffles, with or without a candidate.
148 uint32_t source = 0U;
149 _Pragma("unroll")
150 for (uint32_t step = 16U; step != 0U; step /= 2U) {
151 if (__shfl_sync(~0U, prefix, source + step - 1U) <= candidate) source += step;
152 }
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) {
157 entry_of(
158 (base + source) * 32U + top_k_nth_bit(source_bits, candidate - before),
159 key,
160 value
161 );
162 }
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]) {
166 top_k_replace_root(heap, k, key, value);
167 }
168 __syncwarp();
169 }
170 }
171 }
172}
__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,...
Definition top_k.cuh:66
Here is the call graph for this function:

◆ top_k_word_bits()

template<bool Thresholded, bool UpperTriangle>
__device__ uint32_t cuddl::detail::top_k_word_bits ( batch_result_tile const &  tile,
uint32_t  query,
uint32_t  word,
uint32_t  words 
)
inline

The pass bits of word word of tile row query: the stored bits, or for exhaustive tiles every reference of the word.

Pairs on or below the diagonal of all-to-all tiles are masked.

Definition at line 85 of file top_k.cuh.

85 {
86 uint32_t bits = 0U;
87 if constexpr (Thresholded) {
88 bits = __ldg(tile.pass_bits + static_cast<size_t>(query) * words + word);
89 } else {
90 auto const left = tile.reference_count - word * 32U;
91 bits = left >= 32U ? ~0U : (1U << left) - 1U;
92 }
93 if constexpr (UpperTriangle) {
94 auto const query_id = tile.first_query_id + query;
95 if (word * 32U <= query_id) {
96 auto const skipped = query_id - word * 32U + 1U;
97 bits &= skipped >= 32U ? 0U : ~0U << skipped;
98 }
99 }
100 return bits;
101}

◆ try_unwrap_success() [1/2]

template<typename T >
T cuddl::detail::try_unwrap_success ( Result< T > &  result)

Definition at line 160 of file error.hpp.

160 {
161 return std::move(*result);
162}

◆ try_unwrap_success() [2/2]

void cuddl::detail::try_unwrap_success ( Result< void > &  result)
inline

Definition at line 164 of file error.hpp.

164 {
165 (void)result;
166}

◆ uses_tiled_index_counts()

bool cuddl::detail::uses_tiled_index_counts ( uint32_t  reference_count,
uint32_t  query_count,
uint32_t  indexed_bucket_count 
)
inlinenoexcept

Whether a batch counts through count_batch_index_tile_kernel.

Its 16-bit counters hold at most 65535 matches. Tiny batches finish sooner on the global kernel, which needs no per-block counter initialization or flush.

Definition at line 691 of file kernels.cuh.

695 {
696 return reference_count != 0U && indexed_bucket_count <= 0xffffU &&
697 (reference_count >= 8192U || query_count >= 512U);
698}

◆ validate_indexed_score_compatibility()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::validate_indexed_score_compatibility ( score_compatibility const &  compatibility)
inline

Definition at line 906 of file reference_database.cuh.

908 {
909 CUDDL_TRY((validate_score_compatibility<K, BucketCount, Layout>(compatibility)));
910 auto const full_bucket_count = static_cast<uint32_t>(BucketCount);
911 if (compatibility.indexed_bucket_count != full_bucket_count &&
912 compatibility.indexed_bucket_count != full_bucket_count / 2U) {
913 return Err(Error::invalid_argument("indexed builds require a full or half bucket count"));
914 }
915 if (compatibility.key_mask != 0xffffU && compatibility.key_mask != 0x7fffU) {
916 return Err(Error::invalid_argument("indexed builds require a 16-bit or 15-bit key mask"));
917 }
918 return Ok();
919}
Here is the call graph for this function:

◆ validate_non_indexed_score_compatibility()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::validate_non_indexed_score_compatibility ( score_compatibility const &  compatibility)
inline

Definition at line 892 of file reference_database.cuh.

894 {
895 CUDDL_TRY((validate_score_compatibility<K, BucketCount, Layout>(compatibility)));
896 if (compatibility.indexed_bucket_count != BucketCount) {
897 return Err(Error::invalid_argument("non-indexed builds require every bucket"));
898 }
899 if (compatibility.key_mask != 0xffffU && compatibility.key_mask != 0x7fffU) {
900 return Err(Error::invalid_argument("non-indexed builds require a 16-bit or 15-bit key"));
901 }
902 return Ok();
903}
Here is the call graph for this function:

◆ validate_score_compatibility()

template<uint32_t K, size_t BucketCount, typename Layout = default_register_layout>
Result< void > cuddl::detail::validate_score_compatibility ( score_compatibility const &  compatibility)
inline

Definition at line 868 of file reference_database.cuh.

870 {
871 if (compatibility.kmer_length != K) {
872 return Err(Error::invalid_argument("k-mer length does not match the database type"));
873 }
874 if (compatibility.bucket_count != BucketCount) {
875 return Err(Error::invalid_argument("bucket count does not match the database type"));
876 }
877 if (compatibility.score_encoder_identity == 0U ||
878 compatibility.exponent_bits != Layout::exponent_bits ||
879 compatibility.mantissa_bits != Layout::mantissa_bits) {
880 return Err(Error::invalid_argument("score encoding does not match the register layout"));
881 }
882 if (compatibility.hash_identity == 0U) {
883 return Err(Error::invalid_argument("hash identity must be specified"));
884 }
885 if (compatibility.canonicalisation_policy == 0U) {
886 return Err(Error::invalid_argument("canonicalisation policy must be specified"));
887 }
888 return Ok();
889}
Here is the call graph for this function:

◆ walk_bgzf()

template<typename Block >
std::optional< std::pair< uint32_t, uint64_t > > cuddl::detail::walk_bgzf ( unsigned char const *  bytes,
uint64_t  size,
Block &&  block 
)

Calls block(deflate_at, deflate_bytes, inflated) for each non-empty block of the BGZF file in bytes.

Returns
The non-empty blocks and their inflated total, or nullopt unless whole blocks tile all size bytes.

Definition at line 111 of file device_gzip.cuh.

111 {
112 uint64_t at = 0, total = 0;
113 uint32_t count = 0;
114 while (at < size) {
115 auto const block_size = size - at >= 18 ? bgzf_block_size(bytes + at) : 0;
116 if (block_size < 26 || block_size > size - at) return std::nullopt;
117 uint32_t inflated = 0;
118 std::memcpy(&inflated, bytes + at + block_size - 4, 4);
119 if (inflated != 0) {
120 block(at + 18, uint64_t{block_size} - 26, inflated);
121 total += inflated;
122 ++count;
123 }
124 at += block_size;
125 }
126 return std::pair{count, total};
127}
auto const block
Definition top_k.cuh:236
Here is the call graph for this function:
Here is the caller graph for this function:

◆ warp_grid_blocks()

uint32_t cuddl::detail::warp_grid_blocks ( size_t  warps)
inlinenoexcept

Blocks for a grid-stride launch covering warps warps of block_size threads.

Definition at line 1524 of file kernels.cuh.

1524 {
1525 constexpr size_t warps_per_block = block_size / 32U;
1526 return static_cast<uint32_t>(
1527 cuda::std::min<size_t>((warps + warps_per_block - 1U) / warps_per_block, 65535U)
1528 );
1529}
constexpr uint32_t warps_per_block
Definition kernels.cuh:935
Here is the caller graph for this function:

◆ wide_rows_aligned()

template<typename QueryScore , typename ReferenceScore >
__host__ __device__ bool cuddl::detail::wide_rows_aligned ( QueryScore const *  query,
ReferenceScore const *  reference 
)
inlinenoexcept

True when both row bases meet load_256_alignment on the compiled architecture.

Definition at line 33 of file helpers.cuh.

33 {
34 constexpr uintptr_t mask = load_256_alignment() - 1U;
35 return (reinterpret_cast<uintptr_t>(query) & mask) == 0U &&
36 (reinterpret_cast<uintptr_t>(reference) & mask) == 0U;
37}
__host__ __device__ constexpr uint32_t load_256_alignment() noexcept
Byte alignment load_256_global_nc requires on the compiled architecture.
Definition helpers.cuh:22
Here is the call graph for this function:

◆ write_score_planes()

__device__ __forceinline__ void cuddl::detail::write_score_planes ( uint32_t  score,
uint32_t  group,
uint32_t  lane,
uint32_t *  planes 
)

Writes the bit-planes of one 32-bucket group whose scores the warp holds, one per lane.

Definition at line 1340 of file kernels.cuh.

1340 {
1341 uint32_t mine = 0U;
1342 _Pragma("unroll")
1343 for (uint32_t plane = 0; plane < score_planes; ++plane) {
1344 auto const word = __ballot_sync(0xffffffffU, ((score >> plane) & 1U) != 0U);
1345 if (lane == plane) {
1346 mine = word;
1347 }
1348 }
1349 if (lane < score_planes) {
1350 planes[score_plane_index(group, lane)] = mine;
1351 }
1352}
Here is the call graph for this function:

◆ xxhash64() [1/2]

template<typename T >
__host__ __device__ uint64_t cuddl::detail::xxhash64 ( const T &  key,
uint64_t  seed = 0 
)
inline

Computes xxHash-64 over the raw bytes of a value.

Definition at line 155 of file hash.cuh.

155 {
156 return xxhash64(reinterpret_cast<const uint8_t*>(&key), sizeof(T), seed);
157}
__host__ __device__ uint64_t xxhash64(const uint8_t *bytes, uint64_t size, uint64_t seed)
Computes xxHash-64 over a byte range without allocating or copying it.
Definition hash.cuh:63
Here is the call graph for this function:

◆ xxhash64() [2/2]

__host__ __device__ uint64_t cuddl::detail::xxhash64 ( const uint8_t *  bytes,
uint64_t  size,
uint64_t  seed 
)
inline

Computes xxHash-64 over a byte range without allocating or copying it.

Definition at line 63 of file hash.cuh.

63 {
64 uint64_t offset = 0;
65 uint64_t h64;
66
67 // Process 32-byte chunks
68 if (size >= 32) {
69 uint64_t limit = size - 32;
70 uint64_t v1 = seed + PRIME64_1 + PRIME64_2;
71 uint64_t v2 = seed + PRIME64_2;
72 uint64_t v3 = seed;
73 uint64_t v4 = seed - PRIME64_1;
74
75 do {
76 const uint64_t pipeline_offset = offset / 8;
77 v1 += load_chunk<uint64_t>(bytes, pipeline_offset + 0) * PRIME64_2;
78 v1 = rotl64(v1, 31);
79 v1 *= PRIME64_1;
80 v2 += load_chunk<uint64_t>(bytes, pipeline_offset + 1) * PRIME64_2;
81 v2 = rotl64(v2, 31);
82 v2 *= PRIME64_1;
83 v3 += load_chunk<uint64_t>(bytes, pipeline_offset + 2) * PRIME64_2;
84 v3 = rotl64(v3, 31);
85 v3 *= PRIME64_1;
86 v4 += load_chunk<uint64_t>(bytes, pipeline_offset + 3) * PRIME64_2;
87 v4 = rotl64(v4, 31);
88 v4 *= PRIME64_1;
89 offset += 32;
90 } while (offset <= limit);
91
92 h64 = rotl64(v1, 1) + rotl64(v2, 7) + rotl64(v3, 12) + rotl64(v4, 18);
93
94 v1 *= PRIME64_2;
95 v1 = rotl64(v1, 31);
96 v1 *= PRIME64_1;
97 h64 ^= v1;
98 h64 = h64 * PRIME64_1 + PRIME64_4;
99
100 v2 *= PRIME64_2;
101 v2 = rotl64(v2, 31);
102 v2 *= PRIME64_1;
103 h64 ^= v2;
104 h64 = h64 * PRIME64_1 + PRIME64_4;
105
106 v3 *= PRIME64_2;
107 v3 = rotl64(v3, 31);
108 v3 *= PRIME64_1;
109 h64 ^= v3;
110 h64 = h64 * PRIME64_1 + PRIME64_4;
111
112 v4 *= PRIME64_2;
113 v4 = rotl64(v4, 31);
114 v4 *= PRIME64_1;
115 h64 ^= v4;
116 h64 = h64 * PRIME64_1 + PRIME64_4;
117 } else {
118 h64 = seed + PRIME64_5;
119 }
120
121 h64 += size;
122
123 // Process remaining 8-byte chunks
124 if ((size % 32) >= 8) {
125 for (; offset <= size - 8; offset += 8) {
126 uint64_t k1 = load_chunk<uint64_t>(bytes, offset / 8) * PRIME64_2;
127 k1 = rotl64(k1, 31) * PRIME64_1;
128 h64 ^= k1;
129 h64 = rotl64(h64, 27) * PRIME64_1 + PRIME64_4;
130 }
131 }
132
133 // Process remaining 4-byte chunks
134 if ((size % 8) >= 4) {
135 for (; offset <= size - 4; offset += 4) {
136 h64 ^= (load_chunk<uint32_t>(bytes, offset / 4) & 0xffffffffULL) * PRIME64_1;
137 h64 = rotl64(h64, 23) * PRIME64_2 + PRIME64_3;
138 }
139 }
140
141 // Process remaining bytes
142 if (size % 4) {
143 while (offset < size) {
144 h64 ^= (bytes[offset] & 0xff) * PRIME64_5;
145 h64 = rotl64(h64, 11) * PRIME64_1;
146 ++offset;
147 }
148 }
149
150 return finalize(h64);
151}
constexpr uint64_t PRIME64_1
xxHash-64 round primes.
Definition hash.cuh:33
constexpr uint64_t seed
Domain separation constant folded into every packed k-mer before hashing.
Definition hash.cuh:18
constexpr __host__ __device__ __forceinline__ uint64_t finalize(uint64_t h)
Applies the xxHash-64 final mixing (avalanche) step.
Definition hash.cuh:53
constexpr __host__ __device__ __forceinline__ uint64_t rotl64(uint64_t x, int8_t r)
Rotates x left by r bits.
Definition hash.cuh:40
constexpr uint64_t PRIME64_4
Definition hash.cuh:36
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ ablations

constexpr std::array<std::string_view, 22> cuddl::detail::ablations
inlineconstexpr
Initial value:
{
"co-scheduling",
"read-depth",
"probe-depth",
"pinned-pool",
"numa-pool",
"pinned-reservation",
"input-lanes",
"simd-parsing",
"base-packing",
"kmer-encoding",
"deltas",
"bit-slicing",
"lop3-kernel",
"tile-queue",
"progress-thread",
"match-filter",
"posting-bitmaps",
"index-fallback",
"inverted-index",
"direct-io",
"io-uring",
"write-probe",
}

Optimisations that the CUDDL_ABLATE environment variable can turn off in a build configured with -Dablations=true (CUDDL_ABLATIONS=1).

They exist to measure what each optimisation contributes (scripts/ablation.py); results are the same either way. Other builds compile every switch out.

Definition at line 19 of file ablation.hpp.

19 {
20 "co-scheduling", // nvCOMP claims a batch only if it finishes before the host loaders
21 "read-depth", // readers keep parallel_read_depth reads in flight, not one per loader
22 "probe-depth", // 4 * parallel_read_depth gzip probes in flight, not parallel_read_depth
23 "pinned-pool", // device-only pinned pools that keep their pages, not CCCL-like ones
24 "numa-pool", // device-written pinned buffers on the device's host NUMA node
25 "pinned-reservation", // packed-word buffers pinned before the loaders start
26 "input-lanes", // GPU lanes sized from the input, not a fixed 1 GiB
27 "simd-parsing", // AVX2/AVX-512/NEON line scans, compaction, packing and gzip scans
28 "base-packing", // the CPU path packs bases into two-bit words before copying them
29 "kmer-encoding", // the GPU builds eight windows per thread from shared SWAR-packed tiles
30 "deltas", // near-duplicate rows corrected from a base row, not compared in full
31 "bit-slicing", // exhaustive search on bit-planes, not packed row-major 16-bit scores
32 "lop3-kernel", // the tiled LOP3 plane kernel, not one bucket per lane step
33 "tile-queue", // ranks claim all-to-all tiles from a shared counter, not a fixed split
34 "progress-thread", // rank 0 drives MPI progress for the shared tile counter
35 "match-filter", // a low-plane bound selects candidates before exact refinement
36 "posting-bitmaps", // long posting lists of the inverted index are kept as bitmaps
37 "index-fallback", // an index whose lists cover too many pairs is bypassed
38 "inverted-index", // threshold searches use an inverted index (benchmarks only)
39 "direct-io", // result files are written with O_DIRECT, not through the page cache
40 "io-uring", // direct result writes keep several blocks in flight with io_uring
41 "write-probe", // writers that need not be durable use the page cache while it is faster
42};

◆ ablations_enabled

constexpr bool cuddl::detail::ablations_enabled = false
inlineconstexpr

Definition at line 79 of file ablation.hpp.

◆ all_pairs_block_size

constexpr uint32_t cuddl::detail::all_pairs_block_size = 512U
constexpr

Threads per refine_all_pairs_kernel block.

Two references' planes stay in registers, so a block holds fewer threads than a refine block.

Definition at line 2299 of file kernels.cuh.

◆ all_pairs_word_parts

constexpr uint32_t cuddl::detail::all_pairs_word_parts = 4U
constexpr

Parts a refine_all_pairs_kernel warp claims each 32-reference word in.

Warps idle at the end of a cell until the slowest one finishes its last claim, and words differ in cost, so smaller claims shorten that wait. On a GPU running at its power limit the shorter wait only lowers the clock; with power headroom it shortens the search.

Definition at line 2305 of file kernels.cuh.

◆ all_to_all

static __global__ uint32_t uint32_t uint32_t uint32_t bool cuddl::detail::all_to_all

Definition at line 928 of file kernels.cuh.

◆ batch_query_tile_count

constexpr uint32_t cuddl::detail::batch_query_tile_count = 128U
constexpr

Minimum query rows per batch tile.

Definition at line 955 of file reference_database.cuh.

◆ bitmap_counts

__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 bool uint32_t uint32_t const uint32_t const uint32_t const* cuddl::detail::bitmap_counts
Initial value:
{
constexpr uint32_t warp_width = 32

Definition at line 932 of file kernels.cuh.

◆ bitmap_refine_block_size

constexpr uint32_t cuddl::detail::bitmap_refine_block_size = 768U
constexpr

Threads per refine_batch_bitmap_kernel block.

Leaves register space for query-plane loads alongside cached reference planes.

Definition at line 1649 of file kernels.cuh.

◆ bitmap_refine_dynamic_bytes

template<size_t BucketCount>
constexpr size_t cuddl::detail::bitmap_refine_dynamic_bytes
constexpr
Initial value:
=
bitmap_refine_staged<BucketCount>
? static_cast<size_t>(bitmap_refine_group<BucketCount>) * BucketCount * sizeof(uint16_t)
: 0U

Shared bytes one refine_batch_bitmap_kernel block stages.

Definition at line 1680 of file kernels.cuh.

◆ bitmap_refine_group

template<size_t BucketCount>
constexpr uint32_t cuddl::detail::bitmap_refine_group
constexpr
Initial value:
=
bitmap_refine_staged<BucketCount>
? static_cast<uint32_t>(cuda::std::min<size_t>(
24U,
bitmap_refine_shared_bytes / (BucketCount * sizeof(uint16_t))
))
: 8U

Queries one refine_batch_bitmap_kernel block refines together.

Each reference row is read once per group, so the group size divides the kernel's L2 traffic. Rows too large to stage in shared memory are read from global memory in groups of 8.

Definition at line 1670 of file kernels.cuh.

◆ bitmap_refine_shared_bytes

constexpr size_t cuddl::detail::bitmap_refine_shared_bytes = 96U * 1024U
constexpr

Shared bytes refine_batch_bitmap_kernel may stage query planes in; one block per SM fits on every supported architecture.

Definition at line 1659 of file kernels.cuh.

◆ bitmap_refine_staged

template<size_t BucketCount>
constexpr bool cuddl::detail::bitmap_refine_staged = bitmap_refine_shared_bytes >= BucketCount * sizeof(uint16_t)
constexpr

Whether a query group's planes fit bitmap_refine_shared_bytes.

Definition at line 1663 of file kernels.cuh.

◆ bitmap_refine_waves

constexpr uint32_t cuddl::detail::bitmap_refine_waves = 8U
constexpr

Cells per refine_batch_bitmap_kernel block that a tile of bitmap-selected pairs is split into when it allows.

Selected pairs cluster around near-duplicate genomes, so cells differ in work by 20 times or more; with one cell per block the densest cell sets the kernel's time.

Definition at line 1655 of file kernels.cuh.

◆ bitmap_starts

__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 bool uint32_t uint32_t const uint32_t const* cuddl::detail::bitmap_starts

Definition at line 931 of file kernels.cuh.

◆ bitmaps

__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 bool uint32_t uint32_t const* cuddl::detail::bitmaps

Definition at line 930 of file kernels.cuh.

◆ bits

auto const cuddl::detail::bits
Initial value:
static_cast<size_t>(query_index) * candidate_bit_words(reference_count) +
low / 32U

Definition at line 1184 of file kernels.cuh.

◆ blacklist

__global__ size_t uint32_t bool blacklist_view cuddl::detail::blacklist

Definition at line 96 of file kernels.cuh.

96 {}
97) {
98 __shared__ uint32_t state[BucketCount];
99 for (auto i = threadIdx.x; i < BucketCount; i += blockDim.x) {
100 state[i] = 0U;
101 }
102 __syncthreads();
103
104 auto const process = [&](uint64_t value) {
105 auto const hash = hash_kmer(value);
106 auto const incoming = static_cast<uint32_t>(score<Layout>(hash));
107 auto const bucket = bucket_of<BucketCount>(hash);
108 if constexpr (HasBlacklist) {
109 if (blacklist.contains(value, hash, bucket)) return;
110 }
111 atomicMax(&state[bucket], incoming);
112 };
113
114 auto const stride = static_cast<size_t>(gridDim.x) * blockDim.x * 4U;
115 auto const index = (static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x) * 4U;
116 for (auto offset = index; offset < input_size; offset += stride) {
117 if (vector_input && offset + 4U <= input_size) {
118 uint64_t values[4];
119 load_256_global_nc(input + offset, values);
120 _Pragma("unroll")
121 for (uint32_t item = 0; item < 4U; ++item) {
122 process(values[item]);
123 }
124 } else {
125 // Scalar path: unaligned input keeps striding exactly like the vector path; only
126 // the aligned input's final partial chunk (fewer than four items) ends the loop.
127 _Pragma("unroll")
128 for (uint32_t item = 0; item < 4U; ++item) {
129 if (offset + item < input_size) {
130 process(input[offset + item]);
131 }
132 }
133 if (vector_input) {
134 break;
135 }
136 }
137 }
139
140 // Each CTA starts its merge at a rotated bucket offset (odd multiplier, coprime with
141 // the power-of-two bucket count), so the CTAs' atomic merges interleave across different
142 // addresses instead of all colliding on the same bucket at once. The per-address atomic
143 // serialization then sees a smooth stream of arrivals rather than a synchronized burst.
144 auto const bucket_offset = (static_cast<size_t>(blockIdx.x) * 139U) & (BucketCount - 1U);
145 for (auto j = threadIdx.x; j < BucketCount; j += blockDim.x) {
146 auto const i = (static_cast<size_t>(j) + bucket_offset) & (BucketCount - 1U);
147 auto const stored = state[i];
148 if (stored != 0U) atomicMax(&registers[i], stored);
149 }
150}
__global__ size_t input_size
Definition kernels.cuh:93
auto const stride
Definition kernels.cuh:578

◆ block

auto const cuddl::detail::block = static_cast<uint64_t>(blockIdx.x) * (blockDim.x / 32U) + threadIdx.x / 32U

Definition at line 236 of file top_k.cuh.

◆ block_size

constexpr uint32_t cuddl::detail::block_size = 256
constexpr

Threads per CTA for the single-pair and cardinality reduction kernels.

Definition at line 28 of file kernels.cuh.

◆ cached_groups

constexpr uint32_t cuddl::detail::cached_groups = lane_groups < 2U ? lane_groups : 2U
constexpr

Definition at line 1866 of file kernels.cuh.

◆ candidate_bits

__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const * cuddl::detail::candidate_bits
Initial value:
{
auto const words = candidate_bit_words(reference_count)

Definition at line 929 of file kernels.cuh.

◆ candidate_count

__global__ uint32_t const uint32_t uint32_t const uint32_t const* cuddl::detail::candidate_count

Definition at line 1542 of file kernels.cuh.

◆ candidate_ids

__global__ uint32_t const uint32_t uint32_t const* cuddl::detail::candidate_ids

Definition at line 1541 of file kernels.cuh.

◆ carry

uint32_t cuddl::detail::carry = 0U

Definition at line 547 of file kernels.cuh.

◆ cell_counter

__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t uint32_t uint32_t uint32_t* cuddl::detail::cell_counter
Initial value:
{
constexpr uint32_t warp_width = 32

Definition at line 1855 of file kernels.cuh.

◆ cells

auto const cuddl::detail::cells
Initial value:
= make_refine_cells<UpperTriangle>(
query_id_offset,
query_count,
group,
words_per_query,
max_block_words,
Candidates == refine_candidates::bitmap ? bitmap_refine_waves : 1U
)

Definition at line 1878 of file kernels.cuh.

◆ claimed_cell

__shared__ uint32_t cuddl::detail::claimed_cell

Definition at line 1874 of file kernels.cuh.

◆ column

uint32_t cuddl::detail::column = 0U

Definition at line 247 of file top_k.cuh.

◆ columns

__global__ uint32_t* cuddl::detail::columns

Definition at line 234 of file top_k.cuh.

◆ count

__global__ uint32_t cuddl::detail::count
Initial value:
= [&](uint32_t reference) {
auto const slot = reference - low;
atomicAdd(&packed_counts[slot / 2U], 1U << ((slot % 2U) * 16U));
}

Definition at line 1031 of file kernels.cuh.

1031 {
1032 auto const slot = reference - low;
1033 atomicAdd(&packed_counts[slot / 2U], 1U << ((slot % 2U) * 16U));
1034 };

◆ ddl_cf_size

constexpr uint32_t cuddl::detail::ddl_cf_size = 13
constexpr

Definition at line 25 of file detail/hybrid_cardinality.cuh.

◆ default_arena_ceiling

constexpr size_t cuddl::detail::default_arena_ceiling = size_t{128} << 20
inlineconstexpr

Default arena ceiling.

A batch is sketched only once its arena fills, so an arena that holds the whole corpus leaves the device idle until the last genome loads. Bounding it lets each batch's kernel overlap the loading of the next: on a GH200 building 2048 genomes this cut the build from 239 to 194 ms, and it cost nothing on a 24-thread RTX 5070 Ti host.

Definition at line 117 of file database_staging.cuh.

117{128} << 20;

◆ delta_candidates

constexpr uint32_t cuddl::detail::delta_candidates = delta_signatures * delta_signature_candidates
constexpr

Definition at line 26 of file reference_deltas.cuh.

◆ delta_change_limit

constexpr uint32_t cuddl::detail::delta_change_limit = 160U
constexpr

Most buckets in which a child reference may differ from its base reference.

Definition at line 20 of file reference_deltas.cuh.

◆ delta_child_tile

constexpr uint32_t cuddl::detail::delta_child_tile = 2048U
constexpr

Definition at line 30 of file reference_deltas.cuh.

◆ delta_correction_shared_bytes

template<size_t BucketCount>
constexpr size_t cuddl::detail::delta_correction_shared_bytes
constexpr
Initial value:
=
BucketCount * delta_query_group<BucketCount> * sizeof(uint16_t) +
size_t{delta_correction_warps} * delta_change_limit * sizeof(uint2)
constexpr uint32_t delta_change_limit
Most buckets in which a child reference may differ from its base reference.
constexpr uint32_t delta_correction_warps
Warps per apply_reference_deltas_kernel block, and the most and fewest children one block cell takes.

Definition at line 39 of file reference_deltas.cuh.

41 {delta_correction_warps} * delta_change_limit * sizeof(uint2);

◆ delta_correction_warps

constexpr uint32_t cuddl::detail::delta_correction_warps = 24U
constexpr

Warps per apply_reference_deltas_kernel block, and the most and fewest children one block cell takes.

Definition at line 29 of file reference_deltas.cuh.

◆ delta_min_child_tile

constexpr uint32_t cuddl::detail::delta_min_child_tile = 4U * delta_correction_warps
constexpr

Definition at line 31 of file reference_deltas.cuh.

◆ delta_query_group

template<size_t BucketCount>
constexpr uint32_t cuddl::detail::delta_query_group = static_cast<uint32_t>(32768U / BucketCount)
constexpr

Queries one correction cell stages bucket-major: 64 KiB of scores for every bucket count.

Lane l serves query l % group and every (32 / group)-th change from l / group.

Definition at line 36 of file reference_deltas.cuh.

◆ delta_signature_buckets

constexpr uint32_t cuddl::detail::delta_signature_buckets = 8U
constexpr

Definition at line 23 of file reference_deltas.cuh.

◆ delta_signature_candidates

constexpr uint32_t cuddl::detail::delta_signature_candidates = 4U
constexpr

Highest-ID rows taken from each matching signature.

Definition at line 25 of file reference_deltas.cuh.

◆ delta_signatures

constexpr uint32_t cuddl::detail::delta_signatures = 8U
constexpr

Exact bucket signatures that propose base candidates, and buckets per signature.

Definition at line 22 of file reference_deltas.cuh.

◆ directory

__global__ size_t uint32_t uint16_t const uint32_t const * cuddl::detail::directory
Initial value:
{
auto const entries = sparse_directory_entries(reference_count)

Definition at line 439 of file kernels.cuh.

◆ dlc_blend_high

constexpr double cuddl::detail::dlc_blend_high = 0.952
constexpr

Definition at line 14 of file detail/hybrid_cardinality.cuh.

◆ dlc_blend_low

constexpr double cuddl::detail::dlc_blend_low = 0.088
constexpr

Definition at line 13 of file detail/hybrid_cardinality.cuh.

◆ dlc_info_power

constexpr double cuddl::detail::dlc_info_power = 4.5
constexpr

Definition at line 16 of file detail/hybrid_cardinality.cuh.

◆ dlc_min_fraction

constexpr double cuddl::detail::dlc_min_fraction = 0.002
constexpr

Definition at line 15 of file detail/hybrid_cardinality.cuh.

◆ else

cuddl::detail::else
Initial value:
{
for (auto i = threadIdx.x; i < (width + 1U) / 2U; i += blockDim.x) {
packed_counts[i] = 0U;
}
}
if (threadIdx.x == 0U) {
long_count = 0U;
}
__syncthreads()

Definition at line 1020 of file kernels.cuh.

1020 {
1021 for (auto i = threadIdx.x; i < (width + 1U) / 2U; i += blockDim.x) {
1022 packed_counts[i] = 0U;
1023 }
1024 }
1025 if (threadIdx.x == 0U) {
1026 long_count = 0U;
1027 }

◆ first_query_id

__global__ uint32_t const uint32_t const uint32_t const uint32_t cuddl::detail::first_query_id

Definition at line 1786 of file kernels.cuh.

◆ first_word

__global__ uint32_t uint32_t cuddl::detail::first_word
Initial value:
{
auto const lane = static_cast<uint32_t>(threadIdx.x) % 32U

Definition at line 234 of file top_k.cuh.

◆ group

constexpr uint32_t cuddl::detail::group = QueryGroup
constexpr

Definition at line 1858 of file kernels.cuh.

◆ groups

auto const cuddl::detail::groups = cells.groups

Definition at line 1886 of file kernels.cuh.

◆ groups_per_row

constexpr uint32_t cuddl::detail::groups_per_row = static_cast<uint32_t>(BucketCount / warp_width)
constexpr

Definition at line 1360 of file kernels.cuh.

◆ hash_range

constexpr double cuddl::detail::hash_range = 0x1p64
constexpr

2^64, the range of the 64-bit hash used by the sketch.

Definition at line 10 of file cardinality.cuh.

◆ heap

auto* const cuddl::detail::heap = heaps + warp * top_k_heap_words(slots.k)

Definition at line 200 of file top_k.cuh.

◆ high

auto const cuddl::detail::high = min(low + tile, reference_count)

Definition at line 940 of file kernels.cuh.

◆ hybrid_high

constexpr double cuddl::detail::hybrid_high = 5.0
constexpr

Definition at line 18 of file detail/hybrid_cardinality.cuh.

◆ hybrid_low

constexpr double cuddl::detail::hybrid_low = 0.2
constexpr

Definition at line 17 of file detail/hybrid_cardinality.cuh.

◆ ids

__global__ uint32_t uint32_t uint64_t uint32_t* cuddl::detail::ids
Initial value:
{
constexpr uint32_t spacing =
static_cast<uint32_t>(BucketCount / (delta_signatures * delta_signature_buckets))
constexpr uint32_t delta_signatures
Exact bucket signatures that propose base candidates, and buckets per signature.
constexpr uint32_t delta_signature_buckets

Definition at line 85 of file reference_deltas.cuh.

◆ IncludeCardinality

__global__ size_t uint32_t bool blacklist_view bool cuddl::detail::IncludeCardinality

Definition at line 155 of file kernels.cuh.

◆ index_match_cells_per_warp

constexpr uint32_t cuddl::detail::index_match_cells_per_warp = 32U
constexpr

Cells one warp owns per iteration of count_batch_index_matches_kernel: one per lane.

The host divides the grid by this so every launched warp stays busy.

Definition at line 392 of file kernels.cuh.

◆ index_pair_fraction_limit

constexpr double cuddl::detail::index_pair_fraction_limit = 0.03
constexpr

Index pair fraction (reference_index::pair_fraction) above which batch search compares bit-planes for every pair instead of counting through the index.

Definition at line 952 of file reference_database.cuh.

◆ index_tile_lane_postings

constexpr uint32_t cuddl::detail::index_tile_lane_postings = 16U
constexpr

Longest posting range one count_batch_index_tile_kernel lane walks alone.

Definition at line 682 of file kernels.cuh.

◆ index_tile_long_capacity

constexpr uint32_t cuddl::detail::index_tile_long_capacity = 32U
constexpr

Long posting ranges one count_batch_index_tile_kernel block walks together.

Definition at line 685 of file kernels.cuh.

◆ indexed_bucket_count

__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t cuddl::detail::indexed_bucket_count

Definition at line 438 of file kernels.cuh.

◆ indexed_groups

auto const cuddl::detail::indexed_groups = indexed_bucket_count / warp_width

Definition at line 1594 of file kernels.cuh.

◆ input_size

__global__ size_t cuddl::detail::input_size

Definition at line 93 of file kernels.cuh.

◆ items

auto const cuddl::detail::items = candidate_ids == nullptr ? reference_count : *candidate_count

Definition at line 1549 of file kernels.cuh.

◆ key_count

auto const cuddl::detail::key_count = static_cast<uint32_t>(key_mask) + 1U

Definition at line 472 of file kernels.cuh.

◆ key_mask

__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t cuddl::detail::key_mask

Definition at line 540 of file kernels.cuh.

◆ keys

__global__ uint32_t uint32_t uint64_t* cuddl::detail::keys

Definition at line 84 of file reference_deltas.cuh.

◆ lane

auto const cuddl::detail::lane = static_cast<uint32_t>(threadIdx.x) % warp_width

Definition at line 966 of file kernels.cuh.

◆ lane_groups

constexpr uint32_t cuddl::detail::lane_groups = groups_per_row / warp_width
constexpr

Definition at line 1864 of file kernels.cuh.

◆ Layout

__global__ size_t uint32_t bool blacklist_view bool cuddl::detail::Layout

Definition at line 155 of file kernels.cuh.

157 {
158 using block_reduce = cub::BlockReduce<summary_payload, block_size>;
159 __shared__ typename block_reduce::TempStorage storage;
160
162 for (auto bucket = static_cast<size_t>(threadIdx.x); bucket < BucketCount;
163 bucket += block_size) {
164 auto const left_reg = left[bucket];
165 auto const right_reg = right[bucket];
166 classify(local.counts, left_reg, right_reg);
167 if constexpr (IncludeCardinality) {
168 if (left_reg == 0U) {
169 ++local.empty;
170 } else {
171 local.restored_sum += restore_midpoint<Layout>(static_cast<uint16_t>(left_reg));
172 }
173 }
174 }
175 auto const total = block_reduce(storage).Reduce(local, combine_payloads<IncludeCardinality>);
176 if (threadIdx.x == 0) {
177 output.counts = total.counts;
178 if constexpr (IncludeCardinality) {
179 output.cardinality = cardinality(
180 static_cast<double>(BucketCount),
181 static_cast<double>(total.empty),
182 total.restored_sum
183 );
184 }
185 }
186}
187
193template <size_t BucketCount>
194__global__ __launch_bounds__(block_size) void batch_summary_kernel(
195 uint32_t const* left_rows,
196 uint32_t const* right_rows,
197 size_t pair_count,
198 pairwise_summary* outputs
199) {
200 constexpr uint32_t warp_width = 32U;
201 constexpr uint32_t warps_per_block = block_size / warp_width;
202 using warp_reduce = cub::WarpReduce<pairwise_counts>;
203 __shared__ typename warp_reduce::TempStorage storage[warps_per_block];
204
205 auto const warp = static_cast<uint32_t>(threadIdx.x) / warp_width;
206 auto const lane = static_cast<uint32_t>(threadIdx.x) % warp_width;
207 auto pair = static_cast<size_t>(blockIdx.x) * warps_per_block + warp;
208 auto const pair_stride = static_cast<size_t>(gridDim.x) * warps_per_block;
209 for (; pair < pair_count; pair += pair_stride) {
210 auto const row_offset = pair * BucketCount;
211 pairwise_counts local{};
212 for (auto bucket = static_cast<size_t>(lane); bucket < BucketCount; bucket += warp_width) {
213 classify(local, left_rows[row_offset + bucket], right_rows[row_offset + bucket]);
214 }
215 auto const total = warp_reduce(storage[warp]).Sum(local);
216 if (lane == 0U) {
217 outputs[pair].counts = total;
218 outputs[pair].cardinality = 0.0;
219 }
220 __syncwarp();
221 }
222}
223
227inline __device__ pairwise_counts
228reduce_warp(cg::thread_block_tile<32> const warp, pairwise_counts counts) {
229 counts.lower = cg::reduce(warp, counts.lower, cg::plus<uint32_t>{});
230 counts.equal = cg::reduce(warp, counts.equal, cg::plus<uint32_t>{});
231 counts.higher = cg::reduce(warp, counts.higher, cg::plus<uint32_t>{});
232 counts.both_empty = cg::reduce(warp, counts.both_empty, cg::plus<uint32_t>{});
233 return counts;
234}
235
242template <typename Row>
243__global__ void transpose_indexed_scores_kernel(
244 Row const* rows,
245 uint32_t reference_count,
246 uint32_t indexed_bucket_count,
247 uint32_t full_bucket_count,
248 Row* transposed
249) {
250 constexpr uint32_t tile = 32U;
251 __shared__ Row staging[tile][tile + 1U];
252 auto const bucket = static_cast<uint32_t>(blockIdx.x) * tile + threadIdx.x;
253 auto const reference = static_cast<uint32_t>(blockIdx.y) * tile + threadIdx.y;
254 if (bucket < indexed_bucket_count && reference < reference_count) {
255 staging[threadIdx.y][threadIdx.x] =
256 rows[static_cast<size_t>(reference) * full_bucket_count + bucket];
257 }
259 // Output (bucket, reference) = input (reference, bucket). Threads with consecutive
260 // threadIdx.x must write consecutive references of one bucket, so the output bucket index
261 // comes from threadIdx.y and the output reference from threadIdx.x; the shared-memory read
262 // is the transposed (column) access instead.
263 auto const out_bucket = static_cast<uint32_t>(blockIdx.x) * tile + threadIdx.y;
264 auto const out_reference = static_cast<uint32_t>(blockIdx.y) * tile + threadIdx.x;
265 if (out_bucket < indexed_bucket_count && out_reference < reference_count) {
266 transposed[static_cast<size_t>(out_bucket) * reference_count + out_reference] =
267 staging[threadIdx.x][threadIdx.y];
268 }
269}
270
278template <typename Row>
279__global__ void count_index_cells_bucket_kernel(
280 Row const* transposed,
281 uint32_t indexed_bucket_count,
282 uint32_t reference_count,
283 uint16_t key_mask,
284 uint32_t* cell_counts
285) {
286 extern __shared__ uint32_t key_counts[];
287 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
288 auto const quarter_keys = key_count / 4U;
289 // The exclusive scan also reads the trailing element used for the CSR end offset.
290 if (blockIdx.x == 0U && threadIdx.x == 0U) {
291 cell_counts[static_cast<size_t>(indexed_bucket_count) * key_count] = 0U;
292 }
293 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
294 key_counts[i] = 0U;
295 }
297
298 for (auto bucket = blockIdx.x; bucket < indexed_bucket_count; bucket += gridDim.x) {
299 auto const* bucket_scores = transposed + static_cast<size_t>(bucket) * reference_count;
300 auto* bucket_counts = cell_counts + static_cast<size_t>(bucket) * key_count;
301 for (uint32_t quarter = 0U; quarter < 4U; ++quarter) {
302 auto const key_base = quarter * quarter_keys;
303 for (uint32_t reference = threadIdx.x; reference < reference_count;
304 reference += blockDim.x) {
305 auto const score = static_cast<uint16_t>(__ldcs(&bucket_scores[reference]));
306 if (score == 0U) {
307 continue;
308 }
309 auto const key = static_cast<uint32_t>(score & key_mask);
310 if (key < key_base || key >= key_base + quarter_keys) {
311 continue;
312 }
313 atomicAdd(&key_counts[key - key_base], 1U);
314 }
316 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
317 bucket_counts[key_base + i] = key_counts[i];
318 key_counts[i] = 0U;
319 }
321 }
322 }
323}
324
334template <typename Row>
335__global__ void scatter_index_postings_bucket_kernel(
336 Row const* transposed,
337 uint32_t indexed_bucket_count,
338 uint32_t reference_count,
339 uint16_t key_mask,
340 uint32_t const* offsets,
341 uint32_t* postings
342) {
343 extern __shared__ uint32_t key_ranks[];
344 auto const key_count = static_cast<uint32_t>(key_mask) + 1U;
345 auto const quarter_keys = key_count / 4U;
346 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
347 key_ranks[i] = 0U;
348 }
350
351 for (auto bucket = blockIdx.x; bucket < indexed_bucket_count; bucket += gridDim.x) {
352 auto const* bucket_scores = transposed + static_cast<size_t>(bucket) * reference_count;
353 auto const* bucket_offsets = offsets + static_cast<size_t>(bucket) * key_count;
354 for (uint32_t quarter = 0U; quarter < 4U; ++quarter) {
355 auto const key_base = quarter * quarter_keys;
356 for (uint32_t reference = threadIdx.x; reference < reference_count;
357 reference += blockDim.x) {
358 auto const score = static_cast<uint16_t>(__ldcs(&bucket_scores[reference]));
359 if (score == 0U) {
360 continue;
361 }
362 auto const key = static_cast<uint32_t>(score & key_mask);
363 if (key < key_base || key >= key_base + quarter_keys) {
364 continue;
365 }
366 auto const rank = atomicAdd(&key_ranks[key - key_base], 1U);
367 __stcs(&postings[bucket_offsets[key] + rank], reference);
368 }
370 for (auto i = threadIdx.x; i < quarter_keys; i += blockDim.x) {
371 key_ranks[i] = 0U;
372 }
374 }
375 }
376}
377
379struct minimum_match_predicate {
380 uint32_t const* match_counts;
381 uint32_t minimum_matches;
382
383 [[nodiscard]] __host__ __device__ bool operator()(uint32_t reference_id) const noexcept {
384 return match_counts[reference_id] >= minimum_matches;
385 }
386};
__global__ size_t uint32_t bool blacklist_view bool IncludeCardinality
Definition kernels.cuh:155
__global__ uint16_t const uint32_t uint32_t uint32_t key_count
Definition kernels.cuh:472
auto const warp
Definition kernels.cuh:967
__host__ __device__ double cardinality(double bucket_count, double empty_count, double sum_restored) noexcept
Public DDL cardinality estimate.
__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.
__host__ __device__ uint16_t score(uint64_t hash) noexcept
Computes the 16-bit DDL rarity score for hash.
Definition register.cuh:43
Per-thread accumulator feeding a CUB block reduction for fused summaries.
Definition kernels.cuh:34

◆ local

unsigned long long cuddl::detail::local = 0U

Definition at line 478 of file kernels.cuh.

◆ long_count

__shared__ uint32_t cuddl::detail::long_count

Definition at line 965 of file kernels.cuh.

◆ long_ranges

__shared__ uint2 cuddl::detail::long_ranges[index_tile_long_capacity]

Definition at line 964 of file kernels.cuh.

◆ long_ranges_used

auto const cuddl::detail::long_ranges_used
Initial value:

Definition at line 1152 of file kernels.cuh.

◆ low

auto cuddl::detail::low = static_cast<uint32_t>(blockIdx.x) % tiles * tile

Definition at line 939 of file kernels.cuh.

◆ mantissa_bits

constexpr uint8_t cuddl::detail::mantissa_bits = default_register_layout::mantissa_bits
constexpr

Default score layout retained for source compatibility.

Definition at line 27 of file register.cuh.

◆ mantissa_mask

constexpr uint32_t cuddl::detail::mantissa_mask = (1U << mantissa_bits) - 1U
constexpr

Definition at line 28 of file register.cuh.

◆ match_counts

__global__ uint32_t const * cuddl::detail::match_counts
Initial value:
{
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t uint32_t * range_end_scan
Definition kernels.cuh:543

Definition at line 575 of file kernels.cuh.

◆ match_filter_block_size

constexpr uint32_t cuddl::detail::match_filter_block_size = 512U
constexpr

Threads per match_filter_kernel block.

Four references' low planes stay in registers, so a block holds fewer threads than a refine block.

Definition at line 2090 of file kernels.cuh.

◆ match_filter_planes

constexpr uint32_t cuddl::detail::match_filter_planes = 12U
constexpr

Low planes match_filter_kernel compares.

Definition at line 2110 of file kernels.cuh.

◆ match_filter_supported

template<size_t BucketCount>
constexpr bool cuddl::detail::match_filter_supported
constexpr
Initial value:
=
bitmap_refine_staged<BucketCount> && BucketCount % 1024U == 0U && BucketCount <= 2048U

Whether match_filter_kernel supports BucketCount: a lane caches at most two plane groups of each of its references.

Definition at line 2095 of file kernels.cuh.

◆ max_block_words

constexpr uint32_t cuddl::detail::max_block_words
constexpr
Initial value:
= static_cast<uint32_t>(cuda::std::max<size_t>(
1U, refine_block_row_bytes / (BucketCount * sizeof(uint16_t)) / warp_width
))

Definition at line 1867 of file kernels.cuh.

◆ max_nlz

constexpr uint32_t cuddl::detail::max_nlz = restore_shift_base
constexpr

Definition at line 32 of file register.cuh.

◆ maximum_batch_rows

constexpr size_t cuddl::detail::maximum_batch_rows = (size_t{1} << 31) - 1U
constexpr

Stored sketches one batch launch may index, bounded by the CUDA grid dimension.

Definition at line 31 of file kernels.cuh.

◆ minimum_matches

__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t uint32_t cuddl::detail::minimum_matches

Definition at line 926 of file kernels.cuh.

◆ missing_cell

constexpr uint32_t cuddl::detail::missing_cell = 0x00FFFFFFU
inlineconstexpr

Eight ambiguous bases: an encoder cell past the end of a sequence.

Definition at line 27 of file dna.hpp.

◆ next_word

__shared__ uint32_t cuddl::detail::next_word

Definition at line 1873 of file kernels.cuh.

◆ nlz_bins

constexpr int32_t cuddl::detail::nlz_bins = 66
constexpr

NLZ histogram bins for the hybrid estimator, including the empty tier.

Definition at line 12 of file detail/hybrid_cardinality.cuh.

◆ offsets

__global__ size_t uint32_t const * cuddl::detail::offsets

Definition at line 536 of file kernels.cuh.

◆ packed

__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult* cuddl::detail::packed

Definition at line 1791 of file kernels.cuh.

◆ packed_base

__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t cuddl::detail::packed_base

Definition at line 1790 of file kernels.cuh.

◆ packed_buffer_bytes

constexpr size_t cuddl::detail::packed_buffer_bytes = size_t{2} << 20
inlineconstexpr

Bytes reserved per loader buffer that holds packed bases: a bacterial genome's two-bit words, about 1.5 MB, fit.

Definition at line 208 of file database_staging.cuh.

208{2} << 20;

◆ packed_counts

__shared__ uint32_t cuddl::detail::packed_counts[]
extern

◆ packed_match_counts

__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult uint32_t* cuddl::detail::packed_match_counts
Initial value:
{
constexpr uint32_t warp_width = 32

Definition at line 1792 of file kernels.cuh.

◆ pairs

auto const cuddl::detail::pairs = static_cast<uint64_t>(query_count) * reference_count

Definition at line 1592 of file kernels.cuh.

◆ parallel_read_depth

constexpr size_t cuddl::detail::parallel_read_depth = 64
inlineconstexpr

Reads a parallel file system needs in flight to give one node its bandwidth: on MOGON's Lustre, 16 threads read 2.9 GB/s of ~1 MB files and 64 threads 6.7 GB/s.

Definition at line 1784 of file fastx_sequence_file.hpp.

◆ pass_bits

__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t uint32_t uint32_t * cuddl::detail::pass_bits

Definition at line 1784 of file kernels.cuh.

◆ planes

__global__ uint32_t uint32_t* cuddl::detail::planes
Initial value:
{
constexpr uint32_t warp_width = 32

Definition at line 1358 of file kernels.cuh.

◆ posting_range_block_size

constexpr uint32_t cuddl::detail::posting_range_block_size = 1024U
constexpr

Threads of the single index_posting_ranges_kernel block.

Definition at line 527 of file kernels.cuh.

◆ postings

__global__ size_t uint32_t const uint32_t const * cuddl::detail::postings

Definition at line 574 of file kernels.cuh.

◆ PRIME64_1

constexpr uint64_t cuddl::detail::PRIME64_1 = 11400714785074694791ULL
constexpr

xxHash-64 round primes.

Definition at line 33 of file hash.cuh.

◆ PRIME64_2

constexpr uint64_t cuddl::detail::PRIME64_2 = 14029467366897019727ULL
constexpr

Definition at line 34 of file hash.cuh.

◆ PRIME64_3

constexpr uint64_t cuddl::detail::PRIME64_3 = 1609587929392839161ULL
constexpr

Definition at line 35 of file hash.cuh.

◆ PRIME64_4

constexpr uint64_t cuddl::detail::PRIME64_4 = 9650029242287828579ULL
constexpr

Definition at line 36 of file hash.cuh.

◆ PRIME64_5

constexpr uint64_t cuddl::detail::PRIME64_5 = 2870177450012600261ULL
constexpr

Definition at line 37 of file hash.cuh.

◆ query

auto const cuddl::detail::query = queries + (query_row_offset + query_index) * BucketCount

Definition at line 1030 of file kernels.cuh.

◆ query_count

__global__ uint32_t uint32_t cuddl::detail::query_count

Definition at line 600 of file kernels.cuh.

◆ query_id

auto const cuddl::detail::query_id = tile.first_query_id + query

Definition at line 201 of file top_k.cuh.

◆ query_id_offset

__global__ uint32_t cuddl::detail::query_id_offset

Definition at line 927 of file kernels.cuh.

◆ query_index

auto const cuddl::detail::query_index = static_cast<uint32_t>(blockIdx.x) / tiles

Definition at line 938 of file kernels.cuh.

◆ query_plane_storage

__shared__ uint4 cuddl::detail::query_plane_storage[]
extern

◆ query_row_offset

__global__ size_t cuddl::detail::query_row_offset

Definition at line 599 of file kernels.cuh.

◆ range_begin

__global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t* cuddl::detail::range_begin

Definition at line 541 of file kernels.cuh.

◆ range_end_scan

static __global__ uint32_t const * cuddl::detail::range_end_scan
Initial value:
{
using block_scan = cub::BlockScan<uint32_t, posting_range_block_size>

Definition at line 542 of file kernels.cuh.

◆ ranges

__global__ size_t uint32_t uint16_t const uint32_t const uint32_t uint32_t uint16_t uint2 * cuddl::detail::ranges
Initial value:
{
auto const bucket = static_cast<uint32_t>(blockIdx.x)

Definition at line 925 of file kernels.cuh.

◆ reference_count

__global__ uint32_t uint32_t uint32_t cuddl::detail::reference_count

Definition at line 437 of file kernels.cuh.

◆ reference_mask

__global__ uint32_t uint32_t uint32_t SearchResult uint32_t const* cuddl::detail::reference_mask
Initial value:
{
constexpr uint32_t warp_width = 32

Definition at line 2491 of file kernels.cuh.

◆ reference_planes

__global__ uint32_t uint32_t uint32_t const * cuddl::detail::reference_planes

Definition at line 1539 of file kernels.cuh.

◆ refine_block_row_bytes

constexpr size_t cuddl::detail::refine_block_row_bytes = size_t{8} << 20U
constexpr

Reference-row bytes one refine_batch_index_candidates_kernel block spans.

Candidates are refined one (reference block, query) cell at a time, reference-block major, so the resident warps share a few blocks' rows through L2 instead of every query streaming every candidate reference row from DRAM.

Definition at line 1323 of file kernels.cuh.

1323{8} << 20U;

◆ registers

__global__ size_t uint32_t* cuddl::detail::registers

Definition at line 94 of file kernels.cuh.

◆ restore_shift_base

constexpr uint32_t cuddl::detail::restore_shift_base = 63U - mantissa_bits
constexpr

Default restoration constants retained for source compatibility.

Definition at line 31 of file register.cuh.

◆ result_match_counts

__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t* cuddl::detail::result_match_counts

Definition at line 1852 of file kernels.cuh.

◆ results

__global__ uint32_t uint32_t uint32_t SearchResult * cuddl::detail::results
Initial value:
{
constexpr uint32_t warp_width = 32

Definition at line 1543 of file kernels.cuh.

◆ row

auto const cuddl::detail::row = static_cast<size_t>(query_id - slots.first_genome_id)

Definition at line 202 of file top_k.cuh.

◆ row_count

__global__ size_t cuddl::detail::row_count

Definition at line 1358 of file kernels.cuh.

◆ row_word

auto const cuddl::detail::row_word = static_cast<uint32_t>(block / word_count)

Definition at line 242 of file top_k.cuh.

◆ row_words

auto const cuddl::detail::row_words = static_cast<uint32_t>(BucketCount / 2U)
constexpr

Definition at line 1546 of file kernels.cuh.

◆ rows

__global__ size_t uint16_t* cuddl::detail::rows
Initial value:
{
constexpr uint32_t warp_width = 32

Definition at line 1499 of file kernels.cuh.

◆ score_planes

constexpr uint32_t cuddl::detail::score_planes = 16U
constexpr

Bits per score, and so bit-planes per 32-bucket group.

Definition at line 1326 of file kernels.cuh.

◆ seed

constexpr uint64_t cuddl::detail::seed = 42
constexpr

Domain separation constant folded into every packed k-mer before hashing.

Definition at line 18 of file hash.cuh.

◆ shared_construction_block_size

constexpr uint32_t cuddl::detail::shared_construction_block_size = 768
constexpr

Threads per CTA for the CTA-local construction kernel.

Definition at line 78 of file kernels.cuh.

◆ shift

auto const cuddl::detail::shift = sparse_directory_shift(reference_count)

Definition at line 442 of file kernels.cuh.

◆ signature

__global__ uint32_t uint32_t cuddl::detail::signature

Definition at line 83 of file reference_deltas.cuh.

◆ skipped

auto const cuddl::detail::skipped = UpperTriangle ? (query_id + 1U) / 32U : 0U

Definition at line 206 of file top_k.cuh.

◆ slots

__global__ top_k_slots cuddl::detail::slots
Initial value:
{
extern __shared__ uint64_t heaps[]

Definition at line 195 of file top_k.cuh.

◆ sorted_keys

__global__ size_t uint32_t uint16_t const * cuddl::detail::sorted_keys

Definition at line 469 of file kernels.cuh.

◆ staged

constexpr bool cuddl::detail::staged = bitmap_refine_staged<BucketCount>
constexpr

Definition at line 1860 of file kernels.cuh.

◆ stager_arenas

constexpr size_t cuddl::detail::stager_arenas = 4
inlineconstexpr

Staging arenas the copy stream rotates through, so copies run ahead of the kernels that read earlier arenas while other work shares the device.

Definition at line 121 of file database_staging.cuh.

◆ stager_hold_slots

constexpr size_t cuddl::detail::stager_hold_slots = 16
inlineconstexpr

Host memory the device still has to read must stay alive: the transfer engine's read of a pageable buffer is not ordered with the host writes that follow it.

Sixteen slots take the tail off that wait; at four the tail reached hundreds of milliseconds while dozens of loaders fed one consumer.

Definition at line 204 of file database_staging.cuh.

◆ storage

__shared__ block_scan::TempStorage cuddl::detail::storage

Definition at line 545 of file kernels.cuh.

◆ stride

auto const cuddl::detail::stride = static_cast<uint32_t>(gridDim.x) * block_size

Definition at line 578 of file kernels.cuh.

◆ thresholded

auto const cuddl::detail::thresholded = CountMatches && pass_bits != nullptr

Definition at line 1888 of file kernels.cuh.

◆ tile

__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t cuddl::detail::tile

Definition at line 922 of file kernels.cuh.

◆ tiles

auto const cuddl::detail::tiles = (reference_count + tile - 1U) / tile

Definition at line 937 of file kernels.cuh.

◆ top_k_warps

constexpr uint32_t cuddl::detail::top_k_warps = block_size / 32U
constexpr

Warps per merging block, each holding one heap; fewer when the heaps exceed shared memory.

Definition at line 53 of file top_k.cuh.

◆ total

auto const cuddl::detail::total = static_cast<size_t>(indexed_bucket_count) * entries

Definition at line 443 of file kernels.cuh.

◆ vector_input

__global__ size_t uint32_t bool cuddl::detail::vector_input

Definition at line 95 of file kernels.cuh.

◆ warp

auto const cuddl::detail::warp = static_cast<uint32_t>(threadIdx.x) / warp_width

Definition at line 967 of file kernels.cuh.

◆ warps

auto const cuddl::detail::warps = uint64_t{gridDim.x} * (block_size / warp_width)

Definition at line 2495 of file kernels.cuh.

2495{gridDim.x} * (block_size / warp_width);

◆ warps_per_block

constexpr uint32_t cuddl::detail::warps_per_block = index_tile_block_size / warp_width
constexpr

Definition at line 935 of file kernels.cuh.

◆ wide_chunk_buckets

template<typename QueryScore , typename ReferenceScore >
constexpr uint32_t cuddl::detail::wide_chunk_buckets
constexpr
Initial value:
=
(sizeof(QueryScore) == 2U || sizeof(ReferenceScore) == 2U) ? 16U : 8U

Buckets one contiguous chunk covers, for a query/reference score pair.

One wide load covers 16 buckets whenever either row stores 16-bit scores, and 8 buckets when both store 32-bit registers. The scalar fallback covers half as many.

Definition at line 74 of file kernels.cuh.

◆ width

auto const cuddl::detail::width = high - low

Definition at line 963 of file kernels.cuh.

◆ word

auto const cuddl::detail::word = first_word + static_cast<uint32_t>(block % word_count)

Definition at line 241 of file top_k.cuh.

◆ word_begin

__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t cuddl::detail::word_begin

Definition at line 1788 of file kernels.cuh.

◆ word_count

auto const cuddl::detail::word_count = words - first_word

Definition at line 238 of file top_k.cuh.

◆ word_end

__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t cuddl::detail::word_end

Definition at line 1789 of file kernels.cuh.

◆ word_offsets

__global__ uint32_t const uint32_t const uint32_t const* cuddl::detail::word_offsets

Definition at line 1785 of file kernels.cuh.

◆ word_slices

constexpr uint32_t cuddl::detail::word_slices = Candidates == refine_candidates::all ? 1U : 4U
constexpr

Definition at line 1871 of file kernels.cuh.

◆ words

auto const cuddl::detail::words = tile.words_per_query()

Definition at line 204 of file top_k.cuh.

◆ words_per_query

auto const cuddl::detail::words_per_query = candidate_bit_words(reference_count)

Definition at line 1795 of file kernels.cuh.

◆ work

__global__ uint16_t const uint32_t uint32_t uint32_t unsigned long long* cuddl::detail::work
Initial value:
{
auto const total = offsets != nullptr
? static_cast<size_t>(indexed_bucket_count) * key_count
: static_cast<size_t>(indexed_bucket_count) * reference_count

Definition at line 473 of file kernels.cuh.