71 struct FastxRecordHeaderRef {
73 uint64_t record_index{};
93 "Fused path expects Theta=1 independent query mapping"
97 "Fused path expects horizontal insert mapping across shard words"
108 explicit filter(uint64_t requestedFilterBits)
113 cuda::ceil_div(requestedFilterBits,
Config::filterBlockBits)
117 filter_bits_(num_shards_ *
Config::filterBlockBits),
118 d_shards_(num_shards_) {
139 thrust::raw_pointer_cast(d_shards_.data()),
155 insert_sequence(std::string_view sequence, cuda::stream_ref stream = cudaStream_t{}) {
156 if (record_symbol_count(sequence.size()) <
Config::k) {
160 const uint64_t totalKmers = record_kmer_count(sequence.size());
164 CUSBF_TRY(launch_insert_sequence(d_sequence, stream));
181 cuda::stream_ref stream = cudaStream_t{}
183 const uint64_t totalKmers = sequence_kmer_count(d_sequence);
184 if (totalKmers == 0) {
188 CUSBF_TRY(launch_insert_sequence(d_sequence, stream));
209 uint64_t num_symbols,
210 cuda::stream_ref stream = cudaStream_t{}
213 if (totalKmers == 0) {
217 CUSBF_TRY(launch_insert_dense_packed(d_words, num_symbols, stream));
227 std::span<const uint64_t> words,
228 uint64_t num_symbols,
229 cuda::stream_ref stream = cudaStream_t{}
232 if (totalKmers == 0) {
237 CUSBF_TRY(launch_insert_dense_packed(staged, num_symbols, stream));
250 uint64_t num_symbols,
252 cuda::stream_ref stream = cudaStream_t{}
258 return launch_contains_dense_packed(d_words, num_symbols, d_output, stream);
268 std::span<const uint64_t> words,
269 uint64_t num_symbols,
271 cuda::stream_ref stream = cudaStream_t{}
279 CUSBF_TRY(launch_contains_dense_packed(staged, num_symbols, d_output, stream));
290 std::span<const uint64_t> words,
291 uint64_t num_symbols,
292 cuda::stream_ref stream = cudaStream_t{}
296 return std::vector<uint8_t>{};
299 std::vector<uint8_t> output(numKmers);
301 const device_span<uint8_t> d_output{
308 output.size() *
sizeof(uint8_t),
309 cudaMemcpyDeviceToHost
341 normalize_record_batch_into<Config>(
349 for (
const NormalizedRecord& record : fastx_state_.normalized_records_scratch()) {
355 {fastx_state_.normalized_sequence_scratch().data(),
356 fastx_state_.normalized_sequence_scratch().size()},
387 std::span<const RecordRange> records,
388 cuda::stream_ref stream = cudaStream_t{}
390 CUSBF_TRY(validate_device_record_batch(d_sequence, records));
391 uint64_t totalKmers = 0;
393 totalKmers += record_kmer_count(record.sequenceBytes);
395 if (totalKmers == 0) {
398 CUSBF_TRY(launch_insert_sequence(d_sequence, stream));
418 double fill_fraction = 0.7,
419 cuda::stream_ref stream = cudaStream_t{}
421 return insert_fastx_stream(input,
"<stream>", fill_fraction, stream);
438 const std::filesystem::path& path,
439 double fill_fraction = 0.7,
440 cuda::stream_ref stream = cudaStream_t{}
446 [&](
auto& reader,
auto dispatch_path) {
447 return insert_fastx_reader(
448 reader, std::string_view{path.native()}, fill_fraction, stream, dispatch_path
467 cuda::stream_ref stream = cudaStream_t{}
469 if (sequence_kmer_count(d_sequence) == 0) {
473 return launch_contains_sequence(d_sequence, d_output, stream);
487 std::string_view sequence,
489 cuda::stream_ref stream = cudaStream_t{}
491 if (record_symbol_count(sequence.size()) <
Config::k) {
498 CUSBF_TRY(launch_contains_sequence(d_sequence, d_output, stream));
514 [[nodiscard]] Result<std::vector<uint8_t>>
516 if (record_symbol_count(sequence.size()) <
Config::k) {
517 return std::vector<uint8_t>{};
520 std::vector<uint8_t> output(record_kmer_count(sequence.size()));
522 const device_span<uint8_t> d_output{
529 output.size() *
sizeof(uint8_t),
530 cudaMemcpyDeviceToHost
549 [[nodiscard]] Result<FastxQueryReport>
551 return query_record_batch_aggregate(batch, stream);
567 template <RecordQueryConsumer Consumer>
571 cuda::stream_ref stream = cudaStream_t{}
574 normalize_record_batch_into<Config>(
580 return query_normalized_record_batch_with_hits(
581 batch.
sequence, std::forward<Consumer>(consume), stream
605 std::span<const RecordRange> records,
607 cuda::stream_ref stream = cudaStream_t{}
609 CUSBF_TRY(validate_device_record_batch(d_sequence, records));
610 const uint64_t totalKmers = sequence_kmer_count(d_sequence);
611 if (totalKmers == 0) {
614 if (d_output.size() < totalKmers) {
617 return launch_contains_sequence(d_sequence, d_output, stream);
634 double fill_fraction = 0.7,
635 cuda::stream_ref stream = cudaStream_t{}
637 return query_fastx_stream(input,
"<stream>", fill_fraction, stream);
653 const std::filesystem::path& path,
654 double fill_fraction = 0.7,
655 cuda::stream_ref stream = cudaStream_t{}
661 [&](
auto& reader,
auto dispatch_path) {
662 return query_fastx_reader(
663 reader, std::string_view{path.native()}, fill_fraction, stream, dispatch_path
683 template <FastxRecordConsumer Consumer>
687 double fill_fraction = 0.7,
688 cuda::stream_ref stream = cudaStream_t{}
691 return query_fastx_records_stream(
711 template <FastxRecordConsumer Consumer>
713 const std::filesystem::path& path,
715 double fill_fraction = 0.7,
716 cuda::stream_ref stream = cudaStream_t{}
722 [&](
auto& reader,
auto dispatch_path) {
723 return query_fastx_records_stream(
725 std::string_view{path.native()},
752 double fill_fraction = 0.7,
753 cuda::stream_ref stream = cudaStream_t{}
756 return query_fastx_detailed_stream(
772 const std::filesystem::path& path,
773 double fill_fraction = 0.7,
774 cuda::stream_ref stream = cudaStream_t{}
780 [&](
auto& reader,
auto dispatch_path) {
781 return query_fastx_detailed_stream(
782 reader, std::string_view{path.native()}, fill_fraction, stream, dispatch_path
795 thrust::raw_pointer_cast(d_shards_.data()),
811 const auto* wordsBegin =
812 reinterpret_cast<const uint64_t*
>(thrust::raw_pointer_cast(d_shards_.data()));
814 const uint64_t setBits = thrust::transform_reduce(
817 wordsBegin + totalWords,
818 [] __device__(uint64_t w) -> uint64_t {
return cuda::std::popcount(w); },
820 cuda::std::plus<uint64_t>()
822 return static_cast<float>(setBits) /
static_cast<float>(filter_bits_);
836 uint64_t num_shards_{};
837 uint64_t filter_bits_{};
839 thrust::device_vector<block_type> d_shards_;
840 mutable detail::FastxPipelineState fastx_state_;
848 [[nodiscard]]
static Result<void> validate_device_record_batch(
849 device_span<const char> d_sequence,
850 std::span<const RecordRange> records
852 uint64_t next_offset = 0;
853 for (
const RecordRange& record : records) {
854 if (record.sequenceOffset < next_offset) {
857 "record batch ranges must be ordered and non-overlapping"
861 if (record.sequenceOffset > d_sequence.size() ||
862 record.sequenceBytes > d_sequence.size() - record.sequenceOffset) {
869 "record batch ranges must align to the configured alphabet symbol width"
873 next_offset = record.sequenceOffset + record.sequenceBytes;
878 static Result<void> normalize_record_batch_into_pinned(
879 RecordBatchView batch,
880 detail::FastxPinnedSequenceBuffer& sequence_out,
881 std::vector<NormalizedRecord>& records_out
884 CUSBF_TRY(sequence_out.reserve(
static_cast<size_t>(estimated_bytes)));
885 size_t sequence_out_bytes = 0;
887 batch, sequence_out.data(), sequence_out_bytes, records_out
889 CUSBF_TRY(sequence_out.set_size(sequence_out_bytes));
894 [[nodiscard]] uint64_t size_bytes()
const {
898 [[nodiscard]]
static constexpr uint64_t record_symbol_count(uint64_t bases) {
902 [[nodiscard]]
static constexpr uint64_t record_kmer_count(uint64_t bases) {
906 static void accumulate_insert_report(FastxInsertReport& total,
const FastxInsertReport& chunk) {
907 total.recordsIndexed += chunk.recordsIndexed;
908 total.indexedBases += chunk.indexedBases;
909 total.insertedKmers += chunk.insertedKmers;
912 static void accumulate_query_report(FastxQueryReport& total,
const FastxQueryReport& chunk) {
913 total.recordsQueried += chunk.recordsQueried;
914 total.queriedBases += chunk.queriedBases;
915 total.queriedKmers += chunk.queriedKmers;
916 total.positive_kmers += chunk.positive_kmers;
929 [[nodiscard]] Result<FastxQueryReport>
930 query_record_batch_aggregate(RecordBatchView batch, cuda::stream_ref stream)
const {
932 normalize_record_batch_into<Config>(
934 fastx_state_.normalized_sequence_scratch(),
935 fastx_state_.normalized_records_scratch()
938 return query_normalized_record_batch_aggregate(stream);
951 [[nodiscard]] Result<FastxQueryReport> query_normalized_record_batch_aggregate(
952 cuda::stream_ref stream
954 const detail::QueryLayout layout =
955 detail::QueryLayout::build<Config>(fastx_state_.normalized_records_scratch());
957 FastxQueryReport report;
958 report.recordsQueried = layout.records().size();
959 for (
const detail::QueryLayoutRecord& record : layout.records()) {
960 report.queriedBases += record.size;
961 report.queriedKmers += record.valid_kmers;
963 if (fastx_state_.normalized_sequence_scratch().empty()) {
967 const auto d_sequence =
CUSBF_TRY(fastx_state_.staged_sequence_view(
968 {fastx_state_.normalized_sequence_scratch().data(),
969 fastx_state_.normalized_sequence_scratch().size()},
972 const uint64_t num_kmers = layout.total_hit_count();
973 fastx_state_.ensure_result_capacity(num_kmers);
976 device_span<uint8_t>{
977 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
982 device_span<const uint8_t>{
983 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
988 fastx_state_.release_all();
1006 template <RecordQueryConsumer Consumer>
1007 [[nodiscard]] Result<FastxQueryReport> query_normalized_record_batch_with_hits(
1008 std::string_view input_sequence,
1010 cuda::stream_ref stream
1012 const detail::QueryLayout layout =
1013 detail::QueryLayout::build<Config>(fastx_state_.normalized_records_scratch());
1015 FastxQueryReport report;
1016 report.recordsQueried = layout.records().size();
1017 for (
const detail::QueryLayoutRecord& record : layout.records()) {
1018 report.queriedBases += record.size;
1019 report.queriedKmers += record.valid_kmers;
1021 if (fastx_state_.normalized_sequence_scratch().empty()) {
1025 const auto d_sequence =
CUSBF_TRY(fastx_state_.staged_sequence_view(
1026 {fastx_state_.normalized_sequence_scratch().data(),
1027 fastx_state_.normalized_sequence_scratch().size()},
1030 const uint64_t num_kmers = layout.total_hit_count();
1031 fastx_state_.ensure_result_capacity(num_kmers);
1034 device_span<uint8_t>{
1035 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1040 const uint64_t record_count = layout.records().size();
1041 if (record_count > fastx_state_.query_layout_records_device().size()) {
1042 fastx_state_.query_layout_records_device().resize(record_count);
1044 if (record_count > fastx_state_.record_positive_kmers_device().size()) {
1045 fastx_state_.record_positive_kmers_device().resize(record_count);
1048 thrust::raw_pointer_cast(fastx_state_.query_layout_records_device().data()),
1049 layout.records().data(),
1050 record_count *
sizeof(detail::QueryLayoutRecord),
1051 cudaMemcpyHostToDevice,
1056 device_span<const uint8_t>{
1057 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1059 device_span<const detail::QueryLayoutRecord>{
1060 thrust::raw_pointer_cast(fastx_state_.query_layout_records_device().data()),
1063 device_span<uint64_t>{
1064 thrust::raw_pointer_cast(fastx_state_.record_positive_kmers_device().data()),
1071 if (record_count > fastx_state_.record_positive_kmers_scratch().size()) {
1072 fastx_state_.record_positive_kmers_scratch().resize(record_count);
1075 fastx_state_.record_positive_kmers_scratch().data(),
1076 thrust::raw_pointer_cast(fastx_state_.record_positive_kmers_device().data()),
1077 record_count *
sizeof(uint64_t),
1078 cudaMemcpyDeviceToHost,
1082 fastx_state_.result_hits_scratch().resize(num_kmers);
1084 fastx_state_.result_hits_scratch().data(),
1085 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()),
1086 num_kmers *
sizeof(uint8_t),
1087 cudaMemcpyDeviceToHost,
1092 const auto all_hits = std::span<const uint8_t>{
1093 fastx_state_.result_hits_scratch().data(), fastx_state_.result_hits_scratch().size()
1095 for (
size_t layout_index = 0; layout_index < layout.records().size(); ++layout_index) {
1096 const detail::QueryLayoutRecord& record = layout.records()[layout_index];
1097 const auto sequence = input_sequence.substr(
1098 static_cast<size_t>(record.input_offset),
static_cast<size_t>(record.size)
1100 if (record.hit_count == 0) {
1103 record.record_index,
1108 std::span<const uint8_t>{},
1114 const auto hit_span = layout.hits_for_record(all_hits, layout_index);
1115 const uint64_t positive_kmers =
1116 fastx_state_.record_positive_kmers_scratch()[layout_index];
1117 report.positive_kmers += positive_kmers;
1120 record.record_index,
1129 fastx_state_.release_all();
1134 [[nodiscard]] Result<FastxInsertReport> insert_fastx_stream(
1135 std::istream& input,
1136 std::string_view source_name,
1137 double fill_fraction,
1138 cuda::stream_ref stream
1140 detail::FastxReader reader(input, source_name);
1141 return insert_fastx_reader(
1146 template <
typename FastxReaderType>
1147 [[nodiscard]] Result<FastxInsertReport> insert_fastx_reader(
1148 FastxReaderType& reader,
1149 std::string_view source_name,
1150 double fill_fraction,
1151 cuda::stream_ref stream,
1154 struct InsertAdapter {
1155 using report_type = FastxInsertReport;
1156 [[nodiscard]] detail::fastx_chunk_mode chunk_mode()
const {
1157 return detail::fastx_chunk_mode::insert;
1160 [[nodiscard]]
bool supports_pipelined()
const {
1165 FastxInsertReport report{};
1168 on_record_collected(detail::FastxRecord&, uint64_t, DenseRecordBatchBuilder&)
const {}
1170 [[nodiscard]] Result<void>
1171 flush_sync(DenseRecordBatchBuilder& chunk, cuda::stream_ref stream) {
1172 if (chunk.empty()) {
1175 accumulate_insert_report(
1176 report,
CUSBF_TRY(self.insert_record_batch(chunk.view(), stream))
1178 chunk.clear_and_shrink();
1182 [[nodiscard]] Result<void> flush_pipelined(
1183 DenseRecordBatchBuilder& chunk,
1184 detail::ChunkStreamPair& chunk_streams,
1188 return self.flush_chunk_insert_pipelined(
1189 chunk, report, chunk_streams, ping, has_inflight
1193 [[nodiscard]] Result<void>
1194 finish_pipelined(detail::ChunkStreamPair&,
size_t&,
bool&)
const {
1198 [[nodiscard]] Result<FastxInsertReport> finish() {
1210 InsertAdapter{*
this}
1215 [[nodiscard]] Result<FastxQueryReport> query_fastx_stream(
1216 std::istream& input,
1217 std::string_view source_name,
1218 double fill_fraction,
1219 cuda::stream_ref stream
1221 detail::FastxReader reader(input, source_name);
1222 return query_fastx_reader(
1227 template <
typename FastxReaderType>
1228 [[nodiscard]] Result<FastxQueryReport> query_fastx_reader(
1229 FastxReaderType& reader,
1230 std::string_view source_name,
1231 double fill_fraction,
1232 cuda::stream_ref stream,
1235 struct QueryAdapter {
1236 using report_type = FastxQueryReport;
1237 [[nodiscard]] detail::fastx_chunk_mode chunk_mode()
const {
1238 return detail::fastx_chunk_mode::query;
1241 [[nodiscard]]
bool supports_pipelined()
const {
1246 FastxQueryReport report{};
1249 on_record_collected(detail::FastxRecord&, uint64_t, DenseRecordBatchBuilder&)
const {}
1251 [[nodiscard]] Result<void>
1252 flush_sync(DenseRecordBatchBuilder& chunk, cuda::stream_ref stream) {
1253 if (chunk.empty()) {
1256 accumulate_query_report(
1257 report,
CUSBF_TRY(self.query_record_batch_aggregate(chunk.view(), stream))
1259 chunk.clear_and_shrink();
1263 [[nodiscard]] Result<void> flush_pipelined(
1264 DenseRecordBatchBuilder& chunk,
1265 detail::ChunkStreamPair& chunk_streams,
1269 return self.flush_chunk_query_aggregate_pipelined(
1270 chunk, report, chunk_streams, ping, has_inflight
1274 [[nodiscard]] Result<void>
1275 finish_pipelined(detail::ChunkStreamPair&,
size_t&,
bool&)
const {
1279 [[nodiscard]] Result<FastxQueryReport> finish() {
1295 void accumulate_normalized_insert_report(FastxInsertReport& report,
size_t ping_slot)
const {
1296 const std::vector<NormalizedRecord>& records =
1297 fastx_state_.normalized_records_ping(ping_slot);
1298 report.recordsIndexed += records.size();
1299 for (
const NormalizedRecord& record : records) {
1300 report.indexedBases += record.size;
1301 report.insertedKmers += record.valid_kmers;
1305 void accumulate_normalized_query_report(FastxQueryReport& report,
size_t ping_slot)
const {
1306 const std::vector<NormalizedRecord>& records =
1307 fastx_state_.normalized_records_ping(ping_slot);
1308 report.recordsQueried += records.size();
1309 for (
const NormalizedRecord& record : records) {
1310 report.queriedBases += record.size;
1311 report.queriedKmers += record.valid_kmers;
1315 Result<void> flush_chunk_insert_pipelined(
1316 DenseRecordBatchBuilder& chunk,
1317 FastxInsertReport& report,
1318 detail::ChunkStreamPair& chunk_streams,
1322 if (chunk.empty()) {
1327 CUSBF_CUDA_TRY(cudaStreamSynchronize(chunk_streams[(ping - 1U) & 1U].get()));
1330 const size_t slot = ping & 1U;
1331 const cuda::stream_ref active_stream = chunk_streams[slot];
1333 CUSBF_TRY(normalize_record_batch_into_pinned(
1335 fastx_state_.normalized_sequence_ping(slot),
1336 fastx_state_.normalized_records_ping(slot)
1339 accumulate_normalized_insert_report(report, slot);
1341 if (fastx_state_.normalized_sequence_ping(slot).size() == 0) {
1345 const auto d_sequence =
CUSBF_TRY(fastx_state_.stage_sequence_ping(
1346 ping, fastx_state_.normalized_sequence_ping(slot).view(), active_stream
1348 CUSBF_TRY(launch_insert_sequence(d_sequence, active_stream));
1349 has_inflight =
true;
1354 Result<void> flush_chunk_query_aggregate_pipelined(
1355 DenseRecordBatchBuilder& chunk,
1356 FastxQueryReport& report,
1357 detail::ChunkStreamPair& chunk_streams,
1361 if (chunk.empty()) {
1366 CUSBF_CUDA_TRY(cudaStreamSynchronize(chunk_streams[(ping - 1U) & 1U].get()));
1369 const size_t slot = ping & 1U;
1370 const cuda::stream_ref active_stream = chunk_streams[slot];
1372 CUSBF_TRY(normalize_record_batch_into_pinned(
1374 fastx_state_.normalized_sequence_ping(slot),
1375 fastx_state_.normalized_records_ping(slot)
1378 accumulate_normalized_query_report(report, slot);
1380 if (fastx_state_.normalized_sequence_ping(slot).size() == 0) {
1384 const auto d_sequence =
CUSBF_TRY(fastx_state_.stage_sequence_ping(
1385 ping, fastx_state_.normalized_sequence_ping(slot).view(), active_stream
1387 const uint64_t num_kmers = sequence_kmer_count(d_sequence);
1388 fastx_state_.ensure_result_capacity(num_kmers);
1391 device_span<uint8_t>{
1392 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1397 device_span<const uint8_t>{
1398 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1402 report.positive_kmers += positive_kmers;
1403 has_inflight =
true;
1408 template <
typename FastxReaderType, FastxRecordConsumer Consumer>
1409 [[nodiscard]] Result<FastxQueryReport> query_fastx_records_stream(
1410 FastxReaderType& reader,
1411 std::string_view source_name,
1413 double fill_fraction,
1414 cuda::stream_ref stream,
1417 struct DetailedQueryAdapter {
1418 using report_type = FastxQueryReport;
1420 struct PendingChunk {
1421 std::string sequence{};
1422 std::vector<RecordRange> ranges{};
1423 std::vector<FastxRecordHeaderRef> record_headers{};
1424 detail::QueryLayout layout{};
1425 std::vector<uint64_t> positive_kmers{};
1426 std::vector<uint8_t> hits{};
1427 uint64_t record_index_base{};
1433 record_headers.clear();
1434 layout = detail::QueryLayout{};
1435 positive_kmers.clear();
1437 record_index_base = 0;
1442 [[nodiscard]] detail::fastx_chunk_mode chunk_mode()
const {
1443 return detail::fastx_chunk_mode::query;
1446 [[nodiscard]]
bool supports_pipelined()
const {
1452 FastxQueryReport report{};
1453 std::vector<FastxRecordHeaderRef> record_headers{};
1454 uint64_t record_index_base{0};
1455 PendingChunk pending_chunks[2]{};
1457 DetailedQueryAdapter(
const filter& self_in, Consumer& consume_in)
1458 : self(self_in), consume(consume_in), pending_chunks{} {}
1460 void on_record_collected(
1461 detail::FastxRecord& record,
1462 uint64_t local_index,
1463 DenseRecordBatchBuilder&
1465 record_headers.push_back(
1466 FastxRecordHeaderRef{std::move(record.header), local_index}
1470 [[nodiscard]] Result<void> retire_pending_chunk(
size_t slot) {
1471 PendingChunk& pending = pending_chunks[slot];
1472 if (!pending.ready) {
1476 const auto all_hits =
1477 std::span<const uint8_t>{pending.hits.data(), pending.hits.size()};
1478 for (
size_t layout_index = 0; layout_index < pending.layout.records().size();
1480 const detail::QueryLayoutRecord& record =
1481 pending.layout.records()[layout_index];
1482 const RecordRange& range =
1483 pending.ranges[
static_cast<size_t>(record.record_index)];
1484 const FastxRecordHeaderRef& record_header =
1485 pending.record_headers[
static_cast<size_t>(record.record_index)];
1486 const auto sequence = std::string_view{pending.sequence}.substr(
1487 static_cast<size_t>(range.sequenceOffset),
1488 static_cast<size_t>(range.sequenceBytes)
1490 const auto hit_span =
1491 record.hit_count == 0
1492 ? std::span<const uint8_t>{}
1493 : pending.layout.hits_for_record(all_hits, layout_index);
1494 const uint64_t positive_kmers = pending.positive_kmers[layout_index];
1495 report.positive_kmers += positive_kmers;
1498 pending.record_index_base + record_header.record_index,
1499 record_header.header,
1513 [[nodiscard]] Result<void>
1514 flush_sync(DenseRecordBatchBuilder& chunk, cuda::stream_ref stream) {
1515 if (chunk.empty()) {
1518 const FastxQueryReport chunk_report =
CUSBF_TRY(self.query_record_batch(
1520 [&](
const RecordQueryView& record_view) {
1521 const FastxRecordHeaderRef& record_header =
1522 record_headers[static_cast<size_t>(record_view.record_index)];
1523 const RecordRange& range =
1524 chunk.ranges()[static_cast<size_t>(record_view.record_index)];
1527 record_index_base + record_header.record_index,
1528 record_header.header,
1529 chunk.sequence_view().substr(
1530 static_cast<size_t>(range.sequenceOffset),
1531 static_cast<size_t>(range.sequenceBytes)
1533 record_view.queriedBases,
1534 record_view.queriedKmers,
1535 record_view.positive_kmers,
1542 accumulate_query_report(report, chunk_report);
1543 record_index_base += chunk.recordCount();
1545 record_headers.clear();
1549 [[nodiscard]] Result<void> flush_pipelined(
1550 DenseRecordBatchBuilder& chunk,
1551 detail::ChunkStreamPair& chunk_streams,
1555 if (chunk.empty()) {
1560 const size_t completed_slot = (ping - 1U) & 1U;
1561 CUSBF_CUDA_TRY(cudaStreamSynchronize(chunk_streams[completed_slot].get()));
1562 CUSBF_TRY(retire_pending_chunk(completed_slot));
1563 has_inflight =
false;
1566 const size_t slot = ping & 1U;
1567 const cuda::stream_ref active_stream = chunk_streams[slot];
1568 PendingChunk& pending = pending_chunks[slot];
1570 pending.sequence.assign(chunk.sequence_view());
1571 pending.ranges.assign(chunk.ranges().begin(), chunk.ranges().end());
1572 pending.record_headers = std::move(record_headers);
1573 pending.record_index_base = record_index_base;
1575 CUSBF_TRY(self.normalize_record_batch_into_pinned(
1577 self.fastx_state_.normalized_sequence_ping(slot),
1578 self.fastx_state_.normalized_records_ping(slot)
1580 self.accumulate_normalized_query_report(report, slot);
1581 pending.layout = detail::QueryLayout::build<Config>(
1582 self.fastx_state_.normalized_records_ping(slot)
1585 const uint64_t chunk_records = chunk.recordCount();
1587 record_index_base += chunk_records;
1589 if (self.fastx_state_.normalized_sequence_ping(slot).size() == 0) {
1590 pending.ready =
true;
1595 const auto d_sequence =
CUSBF_TRY(self.fastx_state_.stage_sequence_ping(
1596 ping, self.fastx_state_.normalized_sequence_ping(slot).view(), active_stream
1598 const uint64_t num_kmers = pending.layout.total_hit_count();
1599 self.fastx_state_.ensure_result_capacity(num_kmers);
1600 CUSBF_TRY(self.launch_contains_sequence(
1602 device_span<uint8_t>{
1603 thrust::raw_pointer_cast(self.fastx_state_.result_buffer_device().data()),
1609 const uint64_t record_count = pending.layout.records().size();
1610 if (record_count > self.fastx_state_.query_layout_records_device().size()) {
1611 self.fastx_state_.query_layout_records_device().resize(record_count);
1613 if (record_count > self.fastx_state_.record_positive_kmers_device().size()) {
1614 self.fastx_state_.record_positive_kmers_device().resize(record_count);
1617 thrust::raw_pointer_cast(
1618 self.fastx_state_.query_layout_records_device().data()
1620 pending.layout.records().data(),
1621 record_count *
sizeof(detail::QueryLayoutRecord),
1622 cudaMemcpyHostToDevice,
1627 device_span<const uint8_t>{
1628 thrust::raw_pointer_cast(
1629 self.fastx_state_.result_buffer_device().data()
1633 device_span<const detail::QueryLayoutRecord>{
1634 thrust::raw_pointer_cast(
1635 self.fastx_state_.query_layout_records_device().data()
1639 device_span<uint64_t>{
1640 thrust::raw_pointer_cast(
1641 self.fastx_state_.record_positive_kmers_device().data()
1649 pending.positive_kmers.resize(record_count);
1651 pending.positive_kmers.data(),
1652 thrust::raw_pointer_cast(
1653 self.fastx_state_.record_positive_kmers_device().data()
1655 record_count *
sizeof(uint64_t),
1656 cudaMemcpyDeviceToHost,
1660 pending.hits.resize(num_kmers);
1662 pending.hits.data(),
1663 thrust::raw_pointer_cast(self.fastx_state_.result_buffer_device().data()),
1664 num_kmers *
sizeof(uint8_t),
1665 cudaMemcpyDeviceToHost,
1669 pending.ready =
true;
1670 has_inflight =
true;
1675 [[nodiscard]] Result<void>
1676 finish_pipelined(detail::ChunkStreamPair&,
size_t& ping,
bool& has_inflight) {
1677 if (!has_inflight) {
1681 const size_t completed_slot = (ping - 1U) & 1U;
1682 CUSBF_TRY(retire_pending_chunk(completed_slot));
1683 has_inflight =
false;
1687 [[nodiscard]] Result<FastxQueryReport> finish() {
1699 DetailedQueryAdapter{*
this, consume}
1705 template <
typename FastxReaderType>
1706 [[nodiscard]] Result<FastxDetailedQueryReport> query_fastx_detailed_stream(
1707 FastxReaderType& reader,
1708 std::string_view source_name,
1709 double fill_fraction,
1710 cuda::stream_ref stream,
1713 FastxDetailedQueryReport report;
1714 report.summary =
CUSBF_TRY(query_fastx_records_stream(
1717 [&report](
const FastxRecordView& record) {
1718 report.records.push_back(
1719 FastxDetailedQueryRecord{
1720 record.record_index,
1721 std::string(record.header),
1722 std::string(record.sequence),
1723 record.queriedBases,
1724 record.queriedKmers,
1725 record.positive_kmers,
1726 std::vector<uint8_t>(record.hits.begin(), record.hits.end()),
1738 [[nodiscard]]
static uint64_t sequence_kmer_count(device_span<const char> d_sequence) {
1742 Result<void> launch_insert_dense_packed(
1743 device_span<const uint64_t> d_words,
1744 uint64_t num_symbols,
1745 cuda::stream_ref stream
1748 const uint64_t numKmers = input.kmerCount();
1749 if (numKmers == 0) {
1760 device_span<block_type>{thrust::raw_pointer_cast(d_shards_.data()), num_shards_}
1766 Result<void> launch_contains_dense_packed(
1767 device_span<const uint64_t> d_words,
1768 uint64_t num_symbols,
1769 device_span<uint8_t> d_output,
1770 cuda::stream_ref stream
1773 const uint64_t numKmers = input.kmerCount();
1774 if (numKmers == 0) {
1780 if (d_output.size() < numKmers) {
1784 const uint64_t gridSize =
1789 device_span<const block_type>{
1790 thrust::raw_pointer_cast(d_shards_.data()), num_shards_
1804 launch_insert_sequence(device_span<const char> d_sequence, cuda::stream_ref stream) {
1806 const uint64_t numKmers = input.kmerCount();
1807 if (numKmers == 0) {
1815 device_span<block_type>{thrust::raw_pointer_cast(d_shards_.data()), num_shards_}
1827 Result<void> launch_contains_sequence(
1828 device_span<const char> d_sequence,
1829 device_span<uint8_t> d_output,
1830 cuda::stream_ref stream
1833 const uint64_t numKmers = input.kmerCount();
1834 const uint64_t gridSize =
1840 device_span<const block_type>{
1841 thrust::raw_pointer_cast(d_shards_.data()), num_shards_