cuSBF
Loading...
Searching...
No Matches
filter.cuh
Go to the documentation of this file.
1#pragma once
2
3#include <cuda/__cmath/ceil_div.h>
4#include <cuda_runtime.h>
5
6#include <cuda/std/bit>
7#include <cuda/stream>
8
9#include <thrust/copy.h>
10#include <thrust/detail/execution_policy.h>
11#include <thrust/device_vector.h>
12#include <thrust/execution_policy.h>
13#include <thrust/fill.h>
14#include <thrust/transform_reduce.h>
16
17#include <algorithm>
18#include <array>
19#include <concepts>
20#include <cstddef>
21#include <cstdint>
22#include <filesystem>
23#include <limits>
24#include <span>
25#include <string>
26#include <string_view>
27#include <type_traits>
28#include <utility>
29#include <vector>
30
31#include <cusbf/Alphabet.cuh>
32#include <cusbf/config.cuh>
46#include <cusbf/device_span.cuh>
47#include <cusbf/error.hpp>
48#include <cusbf/Fastx.hpp>
49#include <cusbf/filter_ref.cuh>
50#include <cusbf/hashutil.cuh>
51#include <cusbf/helpers.cuh>
53
54namespace cusbf {
55
68template <typename Config>
69class filter {
70 private:
71 struct FastxRecordHeaderRef {
72 std::string header;
73 uint64_t record_index{};
74 };
75
76 public:
89
90 static_assert(Config::blockWordCount == 4, "Filter only supports the fused 256-bit shard path");
91 static_assert(
93 "Fused path expects Theta=1 independent query mapping"
94 );
95 static_assert(
97 "Fused path expects horizontal insert mapping across shard words"
98 );
99
108 explicit filter(uint64_t requestedFilterBits)
109 : num_shards_(
110 cuda::std::bit_ceil(
111 std::max<uint64_t>(
112 1,
113 cuda::ceil_div(requestedFilterBits, Config::filterBlockBits)
114 )
115 )
116 ),
117 filter_bits_(num_shards_ * Config::filterBlockBits),
118 d_shards_(num_shards_) {
120 }
121
123 filter(const filter&) = delete;
124 filter& operator=(const filter&) = delete;
126 filter(filter&&) noexcept = default;
128 filter& operator=(filter&&) noexcept = default;
130 ~filter() = default;
131
137 [[nodiscard]] filter_ref<Config> ref() const noexcept {
138 return filter_ref<Config>{
139 thrust::raw_pointer_cast(d_shards_.data()),
140 num_shards_,
141 };
142 }
143
154 [[nodiscard]] Result<uint64_t>
155 insert_sequence(std::string_view sequence, cuda::stream_ref stream = cudaStream_t{}) {
156 if (record_symbol_count(sequence.size()) < Config::k) {
157 return 0;
158 }
159
160 const uint64_t totalKmers = record_kmer_count(sequence.size());
161 const auto d_sequence = CUSBF_TRY(
162 fastx_state_.staged_sequence_view({sequence.data(), sequence.size()}, stream)
163 );
164 CUSBF_TRY(launch_insert_sequence(d_sequence, stream));
165 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
166 return totalKmers;
167 }
168
180 device_span<const char> d_sequence,
181 cuda::stream_ref stream = cudaStream_t{}
182 ) {
183 const uint64_t totalKmers = sequence_kmer_count(d_sequence);
184 if (totalKmers == 0) {
185 return 0;
186 }
187
188 CUSBF_TRY(launch_insert_sequence(d_sequence, stream));
189 return totalKmers;
190 }
191
209 uint64_t num_symbols,
210 cuda::stream_ref stream = cudaStream_t{}
211 ) {
212 const uint64_t totalKmers = dense_packed_kmer_count(num_symbols);
213 if (totalKmers == 0) {
214 return 0;
215 }
216
217 CUSBF_TRY(launch_insert_dense_packed(d_words, num_symbols, stream));
218 return totalKmers;
219 }
220
227 std::span<const uint64_t> words,
228 uint64_t num_symbols,
229 cuda::stream_ref stream = cudaStream_t{}
230 ) {
231 const uint64_t totalKmers = dense_packed_kmer_count(num_symbols);
232 if (totalKmers == 0) {
233 return 0;
234 }
235
236 const auto staged = CUSBF_TRY(fastx_state_.staged_dense_packed_view(words, stream));
237 CUSBF_TRY(launch_insert_dense_packed(staged, num_symbols, stream));
238 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
239 return totalKmers;
240 }
241
250 uint64_t num_symbols,
251 device_span<uint8_t> d_output,
252 cuda::stream_ref stream = cudaStream_t{}
253 ) const {
254 if (dense_packed_kmer_count(num_symbols) == 0) {
255 return {};
256 }
257
258 return launch_contains_dense_packed(d_words, num_symbols, d_output, stream);
259 }
260
268 std::span<const uint64_t> words,
269 uint64_t num_symbols,
270 device_span<uint8_t> d_output,
271 cuda::stream_ref stream = cudaStream_t{}
272 ) const {
273 const uint64_t numKmers = dense_packed_kmer_count(num_symbols);
274 if (numKmers == 0) {
275 return {};
276 }
277
278 const auto staged = CUSBF_TRY(fastx_state_.staged_dense_packed_view(words, stream));
279 CUSBF_TRY(launch_contains_dense_packed(staged, num_symbols, d_output, stream));
280 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
281 return {};
282 }
283
290 std::span<const uint64_t> words,
291 uint64_t num_symbols,
292 cuda::stream_ref stream = cudaStream_t{}
293 ) const {
294 const uint64_t numKmers = dense_packed_kmer_count(num_symbols);
295 if (numKmers == 0) {
296 return std::vector<uint8_t>{};
297 }
298
299 std::vector<uint8_t> output(numKmers);
300 fastx_state_.ensure_result_capacity(output.size());
301 const device_span<uint8_t> d_output{
302 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), output.size()
303 };
304 CUSBF_TRY(contains_dense_packed(words, num_symbols, d_output, stream));
305 CUSBF_CUDA_TRY(cudaMemcpy(
306 output.data(),
307 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()),
308 output.size() * sizeof(uint8_t),
309 cudaMemcpyDeviceToHost
310 ));
311 return output;
312 }
313
315 [[nodiscard]] static constexpr uint64_t dense_packed_word_count(uint64_t num_symbols) {
316 return detail::dense_packed_word_count<Config>(num_symbols);
317 }
318
320 [[nodiscard]] constexpr uint64_t dense_packed_kmer_count(uint64_t num_symbols) const {
321 return detail::dense_packed_kmer_count<Config>(num_symbols);
322 }
323
338 [[nodiscard]] Result<FastxInsertReport>
339 insert_record_batch(RecordBatchView batch, cuda::stream_ref stream = cudaStream_t{}) {
340 CUSBF_TRY(
341 normalize_record_batch_into<Config>(
342 batch,
343 fastx_state_.normalized_sequence_scratch(),
344 fastx_state_.normalized_records_scratch()
345 )
346 );
347 FastxInsertReport report;
348 report.recordsIndexed = fastx_state_.normalized_records_scratch().size();
349 for (const NormalizedRecord& record : fastx_state_.normalized_records_scratch()) {
350 report.indexedBases += record.size;
351 report.insertedKmers += record.valid_kmers;
352 }
353 if (!fastx_state_.normalized_sequence_scratch().empty()) {
354 const auto d_sequence = CUSBF_TRY(fastx_state_.staged_sequence_view(
355 {fastx_state_.normalized_sequence_scratch().data(),
356 fastx_state_.normalized_sequence_scratch().size()},
357 stream
358 ));
359 CUSBF_TRY(insert_sequence_async(d_sequence, stream));
360 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
361 }
362 fastx_state_.release_all();
363 return report;
364 }
365
386 device_span<const char> d_sequence,
387 std::span<const RecordRange> records,
388 cuda::stream_ref stream = cudaStream_t{}
389 ) {
390 CUSBF_TRY(validate_device_record_batch(d_sequence, records));
391 uint64_t totalKmers = 0;
392 for (const RecordRange& record : records) {
393 totalKmers += record_kmer_count(record.sequenceBytes);
394 }
395 if (totalKmers == 0) {
396 return 0;
397 }
398 CUSBF_TRY(launch_insert_sequence(d_sequence, stream));
399 return totalKmers;
400 }
401
417 std::istream& input,
418 double fill_fraction = 0.7,
419 cuda::stream_ref stream = cudaStream_t{}
420 ) {
421 return insert_fastx_stream(input, "<stream>", fill_fraction, stream);
422 }
423
438 const std::filesystem::path& path,
439 double fill_fraction = 0.7,
440 cuda::stream_ref stream = cudaStream_t{}
441 ) {
443 path,
445 fill_fraction,
446 [&](auto& reader, auto dispatch_path) {
447 return insert_fastx_reader(
448 reader, std::string_view{path.native()}, fill_fraction, stream, dispatch_path
449 );
450 }
451 );
452 }
453
465 device_span<const char> d_sequence,
466 device_span<uint8_t> d_output,
467 cuda::stream_ref stream = cudaStream_t{}
468 ) const {
469 if (sequence_kmer_count(d_sequence) == 0) {
470 return {};
471 }
472
473 return launch_contains_sequence(d_sequence, d_output, stream);
474 }
475
487 std::string_view sequence,
488 device_span<uint8_t> d_output,
489 cuda::stream_ref stream = cudaStream_t{}
490 ) const {
491 if (record_symbol_count(sequence.size()) < Config::k) {
492 return {};
493 }
494
495 const auto d_sequence = CUSBF_TRY(
496 fastx_state_.staged_sequence_view({sequence.data(), sequence.size()}, stream)
497 );
498 CUSBF_TRY(launch_contains_sequence(d_sequence, d_output, stream));
499 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
500 return {};
501 }
502
514 [[nodiscard]] Result<std::vector<uint8_t>>
515 contains_sequence(std::string_view sequence, cuda::stream_ref stream = cudaStream_t{}) const {
516 if (record_symbol_count(sequence.size()) < Config::k) {
517 return std::vector<uint8_t>{};
518 }
519
520 std::vector<uint8_t> output(record_kmer_count(sequence.size()));
521 fastx_state_.ensure_result_capacity(output.size());
522 const device_span<uint8_t> d_output{
523 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), output.size()
524 };
525 CUSBF_TRY(contains_sequence(sequence, d_output, stream));
526 CUSBF_CUDA_TRY(cudaMemcpy(
527 output.data(),
528 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()),
529 output.size() * sizeof(uint8_t),
530 cudaMemcpyDeviceToHost
531 ));
532 return output;
533 }
534
549 [[nodiscard]] Result<FastxQueryReport>
550 query_record_batch(RecordBatchView batch, cuda::stream_ref stream = cudaStream_t{}) const {
551 return query_record_batch_aggregate(batch, stream);
552 }
553
567 template <RecordQueryConsumer Consumer>
569 RecordBatchView batch,
570 Consumer&& consume,
571 cuda::stream_ref stream = cudaStream_t{}
572 ) const {
573 CUSBF_TRY(
574 normalize_record_batch_into<Config>(
575 batch,
576 fastx_state_.normalized_sequence_scratch(),
577 fastx_state_.normalized_records_scratch()
578 )
579 );
580 return query_normalized_record_batch_with_hits(
581 batch.sequence, std::forward<Consumer>(consume), stream
582 );
583 }
584
604 device_span<const char> d_sequence,
605 std::span<const RecordRange> records,
606 device_span<uint8_t> d_output,
607 cuda::stream_ref stream = cudaStream_t{}
608 ) const {
609 CUSBF_TRY(validate_device_record_batch(d_sequence, records));
610 const uint64_t totalKmers = sequence_kmer_count(d_sequence);
611 if (totalKmers == 0) {
612 return {};
613 }
614 if (d_output.size() < totalKmers) {
615 return Err(Error::invalid_argument("record batch query output span is too small"));
616 }
617 return launch_contains_sequence(d_sequence, d_output, stream);
618 }
619
633 std::istream& input,
634 double fill_fraction = 0.7,
635 cuda::stream_ref stream = cudaStream_t{}
636 ) const {
637 return query_fastx_stream(input, "<stream>", fill_fraction, stream);
638 }
639
653 const std::filesystem::path& path,
654 double fill_fraction = 0.7,
655 cuda::stream_ref stream = cudaStream_t{}
656 ) const {
658 path,
660 fill_fraction,
661 [&](auto& reader, auto dispatch_path) {
662 return query_fastx_reader(
663 reader, std::string_view{path.native()}, fill_fraction, stream, dispatch_path
664 );
665 }
666 );
667 }
668
683 template <FastxRecordConsumer Consumer>
685 std::istream& input,
686 Consumer&& consume,
687 double fill_fraction = 0.7,
688 cuda::stream_ref stream = cudaStream_t{}
689 ) const {
690 detail::FastxReader reader(input, "<stream>");
691 return query_fastx_records_stream(
692 reader,
693 "<stream>",
694 consume,
695 fill_fraction,
696 stream,
698 );
699 }
700
711 template <FastxRecordConsumer Consumer>
713 const std::filesystem::path& path,
714 Consumer&& consume,
715 double fill_fraction = 0.7,
716 cuda::stream_ref stream = cudaStream_t{}
717 ) const {
719 path,
721 fill_fraction,
722 [&](auto& reader, auto dispatch_path) {
723 return query_fastx_records_stream(
724 reader,
725 std::string_view{path.native()},
726 consume,
727 fill_fraction,
728 stream,
729 dispatch_path
730 );
731 }
732 );
733 }
734
751 std::istream& input,
752 double fill_fraction = 0.7,
753 cuda::stream_ref stream = cudaStream_t{}
754 ) const {
755 detail::FastxReader reader(input, "<stream>");
756 return query_fastx_detailed_stream(
757 reader, "<stream>", fill_fraction, stream, detail::fastx_dispatch_path::chunked_stream
758 );
759 }
760
772 const std::filesystem::path& path,
773 double fill_fraction = 0.7,
774 cuda::stream_ref stream = cudaStream_t{}
775 ) const {
777 path,
779 fill_fraction,
780 [&](auto& reader, auto dispatch_path) {
781 return query_fastx_detailed_stream(
782 reader, std::string_view{path.native()}, fill_fraction, stream, dispatch_path
783 );
784 }
785 );
786 }
787
793 [[nodiscard]] Result<void> clear(cuda::stream_ref stream = cudaStream_t{}) {
794 CUSBF_CUDA_TRY(cudaMemsetAsync(
795 thrust::raw_pointer_cast(d_shards_.data()),
796 0,
797 d_shards_.size() * sizeof(block_type),
798 stream.get()
799 ));
800
801 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
802 return {};
803 }
804
810 [[nodiscard]] float load_factor() const {
811 const auto* wordsBegin =
812 reinterpret_cast<const uint64_t*>(thrust::raw_pointer_cast(d_shards_.data()));
813 const uint64_t totalWords = num_shards_ * Config::blockWordCount;
814 const uint64_t setBits = thrust::transform_reduce(
815 thrust::device,
816 wordsBegin,
817 wordsBegin + totalWords,
818 [] __device__(uint64_t w) -> uint64_t { return cuda::std::popcount(w); },
819 uint64_t{0},
820 cuda::std::plus<uint64_t>()
821 );
822 return static_cast<float>(setBits) / static_cast<float>(filter_bits_);
823 }
824
826 [[nodiscard]] uint64_t filter_bits() const {
827 return filter_bits_;
828 }
829
831 [[nodiscard]] uint64_t num_shards() const {
832 return num_shards_;
833 }
834
835 private:
836 uint64_t num_shards_{};
837 uint64_t filter_bits_{};
838
839 thrust::device_vector<block_type> d_shards_;
840 mutable detail::FastxPipelineState fastx_state_;
841
848 [[nodiscard]] static Result<void> validate_device_record_batch(
849 device_span<const char> d_sequence,
850 std::span<const RecordRange> records
851 ) {
852 uint64_t next_offset = 0;
853 for (const RecordRange& record : records) {
854 if (record.sequenceOffset < next_offset) {
855 return Err(
857 "record batch ranges must be ordered and non-overlapping"
858 )
859 );
860 }
861 if (record.sequenceOffset > d_sequence.size() ||
862 record.sequenceBytes > d_sequence.size() - record.sequenceOffset) {
863 return Err(Error::invalid_argument("record batch range exceeds device sequence"));
864 }
865 if (record.sequenceOffset % Config::symbolWidth != 0 ||
866 record.sequenceBytes % Config::symbolWidth != 0) {
867 return Err(
869 "record batch ranges must align to the configured alphabet symbol width"
870 )
871 );
872 }
873 next_offset = record.sequenceOffset + record.sequenceBytes;
874 }
875 return {};
876 }
877
878 static Result<void> normalize_record_batch_into_pinned(
879 RecordBatchView batch,
880 detail::FastxPinnedSequenceBuffer& sequence_out,
881 std::vector<NormalizedRecord>& records_out
882 ) {
883 const uint64_t estimated_bytes = detail::estimate_normalized_batch_bytes<Config>(batch);
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
888 );
889 CUSBF_TRY(sequence_out.set_size(sequence_out_bytes));
890 return {};
891 }
892
894 [[nodiscard]] uint64_t size_bytes() const {
895 return num_shards() * sizeof(block_type);
896 }
897
898 [[nodiscard]] static constexpr uint64_t record_symbol_count(uint64_t bases) {
900 }
901
902 [[nodiscard]] static constexpr uint64_t record_kmer_count(uint64_t bases) {
904 }
905
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;
910 }
911
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;
917 }
918
929 [[nodiscard]] Result<FastxQueryReport>
930 query_record_batch_aggregate(RecordBatchView batch, cuda::stream_ref stream) const {
931 CUSBF_TRY(
932 normalize_record_batch_into<Config>(
933 batch,
934 fastx_state_.normalized_sequence_scratch(),
935 fastx_state_.normalized_records_scratch()
936 )
937 );
938 return query_normalized_record_batch_aggregate(stream);
939 }
940
951 [[nodiscard]] Result<FastxQueryReport> query_normalized_record_batch_aggregate(
952 cuda::stream_ref stream
953 ) const {
954 const detail::QueryLayout layout =
955 detail::QueryLayout::build<Config>(fastx_state_.normalized_records_scratch());
956
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;
962 }
963 if (fastx_state_.normalized_sequence_scratch().empty()) {
964 return report;
965 }
966
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()},
970 stream
971 ));
972 const uint64_t num_kmers = layout.total_hit_count();
973 fastx_state_.ensure_result_capacity(num_kmers);
974 CUSBF_TRY(launch_contains_sequence(
975 d_sequence,
976 device_span<uint8_t>{
977 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
978 },
979 stream
980 ));
981 report.positive_kmers = detail::count_positive_kmers_total<Config>(
982 device_span<const uint8_t>{
983 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
984 },
985 stream
986 );
987 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
988 fastx_state_.release_all();
989 return report;
990 }
991
1006 template <RecordQueryConsumer Consumer>
1007 [[nodiscard]] Result<FastxQueryReport> query_normalized_record_batch_with_hits(
1008 std::string_view input_sequence,
1009 Consumer&& consume,
1010 cuda::stream_ref stream
1011 ) const {
1012 const detail::QueryLayout layout =
1013 detail::QueryLayout::build<Config>(fastx_state_.normalized_records_scratch());
1014
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;
1020 }
1021 if (fastx_state_.normalized_sequence_scratch().empty()) {
1022 return report;
1023 }
1024
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()},
1028 stream
1029 ));
1030 const uint64_t num_kmers = layout.total_hit_count();
1031 fastx_state_.ensure_result_capacity(num_kmers);
1032 CUSBF_TRY(launch_contains_sequence(
1033 d_sequence,
1034 device_span<uint8_t>{
1035 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1036 },
1037 stream
1038 ));
1039
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);
1043 }
1044 if (record_count > fastx_state_.record_positive_kmers_device().size()) {
1045 fastx_state_.record_positive_kmers_device().resize(record_count);
1046 }
1047 CUSBF_CUDA_TRY(cudaMemcpyAsync(
1048 thrust::raw_pointer_cast(fastx_state_.query_layout_records_device().data()),
1049 layout.records().data(),
1050 record_count * sizeof(detail::QueryLayoutRecord),
1051 cudaMemcpyHostToDevice,
1052 stream.get()
1053 ));
1054 CUSBF_TRY(
1056 device_span<const uint8_t>{
1057 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1058 },
1059 device_span<const detail::QueryLayoutRecord>{
1060 thrust::raw_pointer_cast(fastx_state_.query_layout_records_device().data()),
1061 record_count
1062 },
1063 device_span<uint64_t>{
1064 thrust::raw_pointer_cast(fastx_state_.record_positive_kmers_device().data()),
1065 record_count
1066 },
1067 stream
1068 )
1069 );
1070
1071 if (record_count > fastx_state_.record_positive_kmers_scratch().size()) {
1072 fastx_state_.record_positive_kmers_scratch().resize(record_count);
1073 }
1074 CUSBF_CUDA_TRY(cudaMemcpyAsync(
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,
1079 stream.get()
1080 ));
1081
1082 fastx_state_.result_hits_scratch().resize(num_kmers);
1083 CUSBF_CUDA_TRY(cudaMemcpyAsync(
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,
1088 stream.get()
1089 ));
1090 CUSBF_CUDA_TRY(cudaStreamSynchronize(stream.get()));
1091
1092 const auto all_hits = std::span<const uint8_t>{
1093 fastx_state_.result_hits_scratch().data(), fastx_state_.result_hits_scratch().size()
1094 };
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)
1099 );
1100 if (record.hit_count == 0) {
1101 consume(
1102 RecordQueryView{
1103 record.record_index,
1104 sequence,
1105 record.size,
1106 record.valid_kmers,
1107 0,
1108 std::span<const uint8_t>{},
1109 }
1110 );
1111 continue;
1112 }
1113
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;
1118 consume(
1119 RecordQueryView{
1120 record.record_index,
1121 sequence,
1122 record.size,
1123 record.valid_kmers,
1124 positive_kmers,
1125 hit_span,
1126 }
1127 );
1128 }
1129 fastx_state_.release_all();
1130 return report;
1131 }
1132
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
1139 ) {
1140 detail::FastxReader reader(input, source_name);
1141 return insert_fastx_reader(
1142 reader, source_name, fill_fraction, stream, detail::fastx_dispatch_path::chunked_stream
1143 );
1144 }
1145
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,
1152 detail::fastx_dispatch_path dispatch_path
1153 ) {
1154 struct InsertAdapter {
1155 using report_type = FastxInsertReport;
1156 [[nodiscard]] detail::fastx_chunk_mode chunk_mode() const {
1157 return detail::fastx_chunk_mode::insert;
1158 }
1159
1160 [[nodiscard]] bool supports_pipelined() const {
1161 return true;
1162 }
1163
1164 filter& self;
1165 FastxInsertReport report{};
1166
1167 void
1168 on_record_collected(detail::FastxRecord&, uint64_t, DenseRecordBatchBuilder&) const {}
1169
1170 [[nodiscard]] Result<void>
1171 flush_sync(DenseRecordBatchBuilder& chunk, cuda::stream_ref stream) {
1172 if (chunk.empty()) {
1173 return {};
1174 }
1175 accumulate_insert_report(
1176 report, CUSBF_TRY(self.insert_record_batch(chunk.view(), stream))
1177 );
1178 chunk.clear_and_shrink();
1179 return {};
1180 }
1181
1182 [[nodiscard]] Result<void> flush_pipelined(
1183 DenseRecordBatchBuilder& chunk,
1184 detail::ChunkStreamPair& chunk_streams,
1185 size_t& ping,
1186 bool& has_inflight
1187 ) {
1188 return self.flush_chunk_insert_pipelined(
1189 chunk, report, chunk_streams, ping, has_inflight
1190 );
1191 }
1192
1193 [[nodiscard]] Result<void>
1194 finish_pipelined(detail::ChunkStreamPair&, size_t&, bool&) const {
1195 return {};
1196 }
1197
1198 [[nodiscard]] Result<FastxInsertReport> finish() {
1199 return report;
1200 }
1201 };
1202
1204 reader,
1205 source_name,
1206 fill_fraction,
1207 stream,
1208 dispatch_path,
1209 fastx_state_,
1210 InsertAdapter{*this}
1211 );
1212 }
1213
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
1220 ) const {
1221 detail::FastxReader reader(input, source_name);
1222 return query_fastx_reader(
1223 reader, source_name, fill_fraction, stream, detail::fastx_dispatch_path::chunked_stream
1224 );
1225 }
1226
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,
1233 detail::fastx_dispatch_path dispatch_path
1234 ) const {
1235 struct QueryAdapter {
1236 using report_type = FastxQueryReport;
1237 [[nodiscard]] detail::fastx_chunk_mode chunk_mode() const {
1238 return detail::fastx_chunk_mode::query;
1239 }
1240
1241 [[nodiscard]] bool supports_pipelined() const {
1242 return true;
1243 }
1244
1245 const filter& self;
1246 FastxQueryReport report{};
1247
1248 void
1249 on_record_collected(detail::FastxRecord&, uint64_t, DenseRecordBatchBuilder&) const {}
1250
1251 [[nodiscard]] Result<void>
1252 flush_sync(DenseRecordBatchBuilder& chunk, cuda::stream_ref stream) {
1253 if (chunk.empty()) {
1254 return {};
1255 }
1256 accumulate_query_report(
1257 report, CUSBF_TRY(self.query_record_batch_aggregate(chunk.view(), stream))
1258 );
1259 chunk.clear_and_shrink();
1260 return {};
1261 }
1262
1263 [[nodiscard]] Result<void> flush_pipelined(
1264 DenseRecordBatchBuilder& chunk,
1265 detail::ChunkStreamPair& chunk_streams,
1266 size_t& ping,
1267 bool& has_inflight
1268 ) {
1269 return self.flush_chunk_query_aggregate_pipelined(
1270 chunk, report, chunk_streams, ping, has_inflight
1271 );
1272 }
1273
1274 [[nodiscard]] Result<void>
1275 finish_pipelined(detail::ChunkStreamPair&, size_t&, bool&) const {
1276 return {};
1277 }
1278
1279 [[nodiscard]] Result<FastxQueryReport> finish() {
1280 return report;
1281 }
1282 };
1283
1285 reader,
1286 source_name,
1287 fill_fraction,
1288 stream,
1289 dispatch_path,
1290 fastx_state_,
1291 QueryAdapter{*this}
1292 );
1293 }
1294
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;
1302 }
1303 }
1304
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;
1312 }
1313 }
1314
1315 Result<void> flush_chunk_insert_pipelined(
1316 DenseRecordBatchBuilder& chunk,
1317 FastxInsertReport& report,
1318 detail::ChunkStreamPair& chunk_streams,
1319 size_t& ping,
1320 bool& has_inflight
1321 ) {
1322 if (chunk.empty()) {
1323 return {};
1324 }
1325
1326 if (has_inflight) {
1327 CUSBF_CUDA_TRY(cudaStreamSynchronize(chunk_streams[(ping - 1U) & 1U].get()));
1328 }
1329
1330 const size_t slot = ping & 1U;
1331 const cuda::stream_ref active_stream = chunk_streams[slot];
1332
1333 CUSBF_TRY(normalize_record_batch_into_pinned(
1334 chunk.view(),
1335 fastx_state_.normalized_sequence_ping(slot),
1336 fastx_state_.normalized_records_ping(slot)
1337 ));
1338 chunk.clear();
1339 accumulate_normalized_insert_report(report, slot);
1340
1341 if (fastx_state_.normalized_sequence_ping(slot).size() == 0) {
1342 return {};
1343 }
1344
1345 const auto d_sequence = CUSBF_TRY(fastx_state_.stage_sequence_ping(
1346 ping, fastx_state_.normalized_sequence_ping(slot).view(), active_stream
1347 ));
1348 CUSBF_TRY(launch_insert_sequence(d_sequence, active_stream));
1349 has_inflight = true;
1350 ping += 1;
1351 return {};
1352 }
1353
1354 Result<void> flush_chunk_query_aggregate_pipelined(
1355 DenseRecordBatchBuilder& chunk,
1356 FastxQueryReport& report,
1357 detail::ChunkStreamPair& chunk_streams,
1358 size_t& ping,
1359 bool& has_inflight
1360 ) const {
1361 if (chunk.empty()) {
1362 return {};
1363 }
1364
1365 if (has_inflight) {
1366 CUSBF_CUDA_TRY(cudaStreamSynchronize(chunk_streams[(ping - 1U) & 1U].get()));
1367 }
1368
1369 const size_t slot = ping & 1U;
1370 const cuda::stream_ref active_stream = chunk_streams[slot];
1371
1372 CUSBF_TRY(normalize_record_batch_into_pinned(
1373 chunk.view(),
1374 fastx_state_.normalized_sequence_ping(slot),
1375 fastx_state_.normalized_records_ping(slot)
1376 ));
1377 chunk.clear();
1378 accumulate_normalized_query_report(report, slot);
1379
1380 if (fastx_state_.normalized_sequence_ping(slot).size() == 0) {
1381 return {};
1382 }
1383
1384 const auto d_sequence = CUSBF_TRY(fastx_state_.stage_sequence_ping(
1385 ping, fastx_state_.normalized_sequence_ping(slot).view(), active_stream
1386 ));
1387 const uint64_t num_kmers = sequence_kmer_count(d_sequence);
1388 fastx_state_.ensure_result_capacity(num_kmers);
1389 CUSBF_TRY(launch_contains_sequence(
1390 d_sequence,
1391 device_span<uint8_t>{
1392 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1393 },
1394 active_stream
1395 ));
1396 const uint64_t positive_kmers = detail::count_positive_kmers_total<Config>(
1397 device_span<const uint8_t>{
1398 thrust::raw_pointer_cast(fastx_state_.result_buffer_device().data()), num_kmers
1399 },
1400 active_stream
1401 );
1402 report.positive_kmers += positive_kmers;
1403 has_inflight = true;
1404 ping += 1;
1405 return {};
1406 }
1407
1408 template <typename FastxReaderType, FastxRecordConsumer Consumer>
1409 [[nodiscard]] Result<FastxQueryReport> query_fastx_records_stream(
1410 FastxReaderType& reader,
1411 std::string_view source_name,
1412 Consumer&& consume,
1413 double fill_fraction,
1414 cuda::stream_ref stream,
1415 detail::fastx_dispatch_path dispatch_path
1416 ) const {
1417 struct DetailedQueryAdapter {
1418 using report_type = FastxQueryReport;
1419
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{};
1428 bool ready{};
1429
1430 void clear() {
1431 sequence.clear();
1432 ranges.clear();
1433 record_headers.clear();
1434 layout = detail::QueryLayout{};
1435 positive_kmers.clear();
1436 hits.clear();
1437 record_index_base = 0;
1438 ready = false;
1439 }
1440 };
1441
1442 [[nodiscard]] detail::fastx_chunk_mode chunk_mode() const {
1443 return detail::fastx_chunk_mode::query;
1444 }
1445
1446 [[nodiscard]] bool supports_pipelined() const {
1447 return true;
1448 }
1449
1450 const filter& self;
1451 Consumer& consume;
1452 FastxQueryReport report{};
1453 std::vector<FastxRecordHeaderRef> record_headers{};
1454 uint64_t record_index_base{0};
1455 PendingChunk pending_chunks[2]{};
1456
1457 DetailedQueryAdapter(const filter& self_in, Consumer& consume_in)
1458 : self(self_in), consume(consume_in), pending_chunks{} {}
1459
1460 void on_record_collected(
1461 detail::FastxRecord& record,
1462 uint64_t local_index,
1463 DenseRecordBatchBuilder&
1464 ) {
1465 record_headers.push_back(
1466 FastxRecordHeaderRef{std::move(record.header), local_index}
1467 );
1468 }
1469
1470 [[nodiscard]] Result<void> retire_pending_chunk(size_t slot) {
1471 PendingChunk& pending = pending_chunks[slot];
1472 if (!pending.ready) {
1473 return {};
1474 }
1475
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();
1479 ++layout_index) {
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)
1489 );
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;
1496 consume(
1497 FastxRecordView{
1498 pending.record_index_base + record_header.record_index,
1499 record_header.header,
1500 sequence,
1501 record.size,
1502 record.valid_kmers,
1503 positive_kmers,
1504 hit_span,
1505 }
1506 );
1507 }
1508
1509 pending.clear();
1510 return {};
1511 }
1512
1513 [[nodiscard]] Result<void>
1514 flush_sync(DenseRecordBatchBuilder& chunk, cuda::stream_ref stream) {
1515 if (chunk.empty()) {
1516 return {};
1517 }
1518 const FastxQueryReport chunk_report = CUSBF_TRY(self.query_record_batch(
1519 chunk.view(),
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)];
1525 consume(
1526 FastxRecordView{
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)
1532 ),
1533 record_view.queriedBases,
1534 record_view.queriedKmers,
1535 record_view.positive_kmers,
1536 record_view.hits,
1537 }
1538 );
1539 },
1540 stream
1541 ));
1542 accumulate_query_report(report, chunk_report);
1543 record_index_base += chunk.recordCount();
1544 chunk.clear();
1545 record_headers.clear();
1546 return {};
1547 }
1548
1549 [[nodiscard]] Result<void> flush_pipelined(
1550 DenseRecordBatchBuilder& chunk,
1551 detail::ChunkStreamPair& chunk_streams,
1552 size_t& ping,
1553 bool& has_inflight
1554 ) {
1555 if (chunk.empty()) {
1556 return {};
1557 }
1558
1559 if (has_inflight) {
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;
1564 }
1565
1566 const size_t slot = ping & 1U;
1567 const cuda::stream_ref active_stream = chunk_streams[slot];
1568 PendingChunk& pending = pending_chunks[slot];
1569 pending.clear();
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;
1574
1575 CUSBF_TRY(self.normalize_record_batch_into_pinned(
1576 chunk.view(),
1577 self.fastx_state_.normalized_sequence_ping(slot),
1578 self.fastx_state_.normalized_records_ping(slot)
1579 ));
1580 self.accumulate_normalized_query_report(report, slot);
1581 pending.layout = detail::QueryLayout::build<Config>(
1582 self.fastx_state_.normalized_records_ping(slot)
1583 );
1584
1585 const uint64_t chunk_records = chunk.recordCount();
1586 chunk.clear();
1587 record_index_base += chunk_records;
1588
1589 if (self.fastx_state_.normalized_sequence_ping(slot).size() == 0) {
1590 pending.ready = true;
1591 CUSBF_TRY(retire_pending_chunk(slot));
1592 return {};
1593 }
1594
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
1597 ));
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(
1601 d_sequence,
1602 device_span<uint8_t>{
1603 thrust::raw_pointer_cast(self.fastx_state_.result_buffer_device().data()),
1604 num_kmers
1605 },
1606 active_stream
1607 ));
1608
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);
1612 }
1613 if (record_count > self.fastx_state_.record_positive_kmers_device().size()) {
1614 self.fastx_state_.record_positive_kmers_device().resize(record_count);
1615 }
1616 CUSBF_CUDA_TRY(cudaMemcpyAsync(
1617 thrust::raw_pointer_cast(
1618 self.fastx_state_.query_layout_records_device().data()
1619 ),
1620 pending.layout.records().data(),
1621 record_count * sizeof(detail::QueryLayoutRecord),
1622 cudaMemcpyHostToDevice,
1623 active_stream.get()
1624 ));
1625 CUSBF_TRY(
1627 device_span<const uint8_t>{
1628 thrust::raw_pointer_cast(
1629 self.fastx_state_.result_buffer_device().data()
1630 ),
1631 num_kmers
1632 },
1633 device_span<const detail::QueryLayoutRecord>{
1634 thrust::raw_pointer_cast(
1635 self.fastx_state_.query_layout_records_device().data()
1636 ),
1637 record_count
1638 },
1639 device_span<uint64_t>{
1640 thrust::raw_pointer_cast(
1641 self.fastx_state_.record_positive_kmers_device().data()
1642 ),
1643 record_count
1644 },
1645 active_stream
1646 )
1647 );
1648
1649 pending.positive_kmers.resize(record_count);
1650 CUSBF_CUDA_TRY(cudaMemcpyAsync(
1651 pending.positive_kmers.data(),
1652 thrust::raw_pointer_cast(
1653 self.fastx_state_.record_positive_kmers_device().data()
1654 ),
1655 record_count * sizeof(uint64_t),
1656 cudaMemcpyDeviceToHost,
1657 active_stream.get()
1658 ));
1659
1660 pending.hits.resize(num_kmers);
1661 CUSBF_CUDA_TRY(cudaMemcpyAsync(
1662 pending.hits.data(),
1663 thrust::raw_pointer_cast(self.fastx_state_.result_buffer_device().data()),
1664 num_kmers * sizeof(uint8_t),
1665 cudaMemcpyDeviceToHost,
1666 active_stream.get()
1667 ));
1668
1669 pending.ready = true;
1670 has_inflight = true;
1671 ping += 1;
1672 return {};
1673 }
1674
1675 [[nodiscard]] Result<void>
1676 finish_pipelined(detail::ChunkStreamPair&, size_t& ping, bool& has_inflight) {
1677 if (!has_inflight) {
1678 return {};
1679 }
1680
1681 const size_t completed_slot = (ping - 1U) & 1U;
1682 CUSBF_TRY(retire_pending_chunk(completed_slot));
1683 has_inflight = false;
1684 return {};
1685 }
1686
1687 [[nodiscard]] Result<FastxQueryReport> finish() {
1688 return report;
1689 }
1690 };
1691
1693 reader,
1694 source_name,
1695 fill_fraction,
1696 stream,
1697 dispatch_path,
1698 fastx_state_,
1699 DetailedQueryAdapter{*this, consume}
1700 );
1701 }
1702
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,
1711 detail::fastx_dispatch_path dispatch_path
1712 ) const {
1713 FastxDetailedQueryReport report;
1714 report.summary = CUSBF_TRY(query_fastx_records_stream(
1715 reader,
1716 source_name,
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()),
1727 }
1728 );
1729 },
1730 fill_fraction,
1731 stream,
1732 dispatch_path
1733 ));
1734 return report;
1735 }
1736
1738 [[nodiscard]] static uint64_t sequence_kmer_count(device_span<const char> d_sequence) {
1739 return detail::SequenceKmerInput<Config>{d_sequence}.kmerCount();
1740 }
1741
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
1746 ) {
1747 const detail::DensePackedKmerInput<Config> input{d_words, num_symbols};
1748 const uint64_t numKmers = input.kmerCount();
1749 if (numKmers == 0) {
1750 return {};
1751 }
1752 if (d_words.size() < dense_packed_word_count(num_symbols)) {
1753 return Err(Error::invalid_argument("dense packed span is too small for num_symbols"));
1754 }
1755
1756 const uint64_t gridSize = cuda::ceil_div(numKmers, Config::cudaBlockSize);
1758 <<<gridSize, Config::cudaBlockSize, 0, stream.get()>>>(
1759 input,
1760 device_span<block_type>{thrust::raw_pointer_cast(d_shards_.data()), num_shards_}
1761 );
1762 CUSBF_CUDA_TRY(cudaGetLastError());
1763 return {};
1764 }
1765
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
1771 ) const {
1772 const detail::DensePackedKmerInput<Config> input{d_words, num_symbols};
1773 const uint64_t numKmers = input.kmerCount();
1774 if (numKmers == 0) {
1775 return {};
1776 }
1777 if (d_words.size() < dense_packed_word_count(num_symbols)) {
1778 return Err(Error::invalid_argument("dense packed span is too small for num_symbols"));
1779 }
1780 if (d_output.size() < numKmers) {
1781 return Err(Error::invalid_argument("dense packed query output span is too small"));
1782 }
1783
1784 const uint64_t gridSize =
1785 cuda::ceil_div(numKmers, Config::cudaBlockSize * detail::kContainsSequenceStride);
1787 <<<gridSize, Config::cudaBlockSize, 0, stream.get()>>>(
1788 input,
1789 device_span<const block_type>{
1790 thrust::raw_pointer_cast(d_shards_.data()), num_shards_
1791 },
1792 d_output
1793 );
1794 CUSBF_CUDA_TRY(cudaGetLastError());
1795 return {};
1796 }
1797
1803 Result<void>
1804 launch_insert_sequence(device_span<const char> d_sequence, cuda::stream_ref stream) {
1805 const auto input = detail::SequenceKmerInput<Config>{d_sequence};
1806 const uint64_t numKmers = input.kmerCount();
1807 if (numKmers == 0) {
1808 return {};
1809 }
1810 const uint64_t gridSize = cuda::ceil_div(numKmers, Config::cudaBlockSize);
1811
1813 <<<gridSize, Config::cudaBlockSize, 0, stream.get()>>>(
1814 input,
1815 device_span<block_type>{thrust::raw_pointer_cast(d_shards_.data()), num_shards_}
1816 );
1817 CUSBF_CUDA_TRY(cudaGetLastError());
1818 return {};
1819 }
1820
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
1831 ) const {
1832 const auto input = detail::SequenceKmerInput<Config>{d_sequence};
1833 const uint64_t numKmers = input.kmerCount();
1834 const uint64_t gridSize =
1835 cuda::ceil_div(numKmers, Config::cudaBlockSize * detail::kContainsSequenceStride);
1836
1838 <<<gridSize, Config::cudaBlockSize, 0, stream.get()>>>(
1839 input,
1840 device_span<const block_type>{
1841 thrust::raw_pointer_cast(d_shards_.data()), num_shards_
1842 },
1843 d_output
1844 );
1845 CUSBF_CUDA_TRY(cudaGetLastError());
1846 return {};
1847 }
1848};
1849
1850} // namespace cusbf
Result< device_span< const uint64_t > > staged_dense_packed_view(std::span< const uint64_t > words, cuda::stream_ref stream)
Result< device_span< const char > > staged_sequence_view(std::span< const char > sequence, cuda::stream_ref stream)
std::vector< NormalizedRecord > & normalized_records_scratch() noexcept
thrust::device_vector< uint8_t > & result_buffer_device() noexcept
std::string & normalized_sequence_scratch() noexcept
Streaming FASTA/FASTQ parser.
Definition Fastx.hpp:297
Non-owning device reference to sectorized filter storage.
GPU Super Bloom filter (defined in filter.cuh).
Definition filter.cuh:69
filter(filter &&) noexcept=default
Move-constructs, transfers shard vectors and staging buffers.
Result< FastxInsertReport > insert_fastx_file(const std::filesystem::path &path, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{})
Inserts all k-mers from a FASTA/FASTQ file.
Definition filter.cuh:437
Result< uint64_t > insert_record_batch_async(device_span< const char > d_sequence, std::span< const RecordRange > records, cuda::stream_ref stream=cudaStream_t{})
Async insert of k-mers from a device-resident record batch.
Definition filter.cuh:385
uint64_t filter_bits() const
Returns the total allocated capacity of the filter in bits.
Definition filter.cuh:826
filter(uint64_t requestedFilterBits)
Constructs a Filter with at least requestedFilterBits bits of storage.
Definition filter.cuh:108
Result< std::vector< uint8_t > > contains_dense_packed(std::span< const uint64_t > words, uint64_t num_symbols, cuda::stream_ref stream=cudaStream_t{}) const
Queries all k-mers from a host-resident dense packed symbol buffer.
Definition filter.cuh:289
constexpr uint64_t dense_packed_kmer_count(uint64_t num_symbols) const
Number of k-mer windows in a dense packed sequence of num_symbols symbols.
Definition filter.cuh:320
Result< std::vector< uint8_t > > contains_sequence(std::string_view sequence, cuda::stream_ref stream=cudaStream_t{}) const
Queries all valid k-mers from a host-resident sequence.
Definition filter.cuh:515
Result< void > contains_record_batch_async(device_span< const char > d_sequence, std::span< const RecordRange > records, device_span< uint8_t > d_output, cuda::stream_ref stream=cudaStream_t{}) const
Async query of k-mers from a device-resident record batch.
Definition filter.cuh:603
Result< FastxDetailedQueryReport > query_fastx_detailed(std::istream &input, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{}) const
Queries all k-mers from a FASTA/FASTQ input stream via chunked streaming and preserves per-record hit...
Definition filter.cuh:750
uint64_t num_shards() const
Returns the number of shards.
Definition filter.cuh:831
Result< FastxQueryReport > query_record_batch(RecordBatchView batch, cuda::stream_ref stream=cudaStream_t{}) const
Queries a dense host-resident record batch and returns aggregate counts.
Definition filter.cuh:550
Result< FastxQueryReport > query_fastx_file_records(const std::filesystem::path &path, Consumer &&consume, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{}) const
Queries a FASTA/FASTQ file and emits one record result per parsed record.
Definition filter.cuh:712
Result< uint64_t > insert_sequence_async(device_span< const char > d_sequence, cuda::stream_ref stream=cudaStream_t{})
Async insert of k-mers from a device-resident sequence.
Definition filter.cuh:179
Result< void > contains_dense_packed(std::span< const uint64_t > words, uint64_t num_symbols, device_span< uint8_t > d_output, cuda::stream_ref stream=cudaStream_t{}) const
Queries all k-mers from a host-resident dense packed symbol buffer into device memory.
Definition filter.cuh:267
filter_block< Config > block_type
One 256-bit filter block stored as an array of Config::blockWordCount words.
Definition filter.cuh:86
Result< FastxQueryReport > query_fastx_file(const std::filesystem::path &path, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{}) const
Queries all k-mers from a FASTA/FASTQ file.
Definition filter.cuh:652
Result< FastxInsertReport > insert_fastx(std::istream &input, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{})
Inserts all k-mers from a FASTA/FASTQ input stream.
Definition filter.cuh:416
Result< uint64_t > insert_sequence(std::string_view sequence, cuda::stream_ref stream=cudaStream_t{})
Inserts all valid k-mers from a host-resident sequence.
Definition filter.cuh:155
static constexpr uint64_t dense_packed_word_count(uint64_t num_symbols)
Number of uint64_t words for num_symbols dense packed symbols.
Definition filter.cuh:315
Result< uint64_t > insert_dense_packed(std::span< const uint64_t > words, uint64_t num_symbols, cuda::stream_ref stream=cudaStream_t{})
Inserts all k-mers from a host-resident dense packed symbol buffer.
Definition filter.cuh:226
Result< FastxInsertReport > insert_record_batch(RecordBatchView batch, cuda::stream_ref stream=cudaStream_t{})
Inserts a dense host-resident record batch.
Definition filter.cuh:339
Result< FastxQueryReport > query_fastx_records(std::istream &input, Consumer &&consume, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{}) const
Queries a FASTA/FASTQ stream and emits one record result per parsed record.
Definition filter.cuh:684
Result< FastxDetailedQueryReport > query_fastx_file_detailed(const std::filesystem::path &path, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{}) const
Queries all k-mers from a FASTA/FASTQ file via chunked streaming and preserves per-record hit vectors...
Definition filter.cuh:771
Result< void > clear(cuda::stream_ref stream=cudaStream_t{})
Resets all filter bits to zero and synchronises the stream.
Definition filter.cuh:793
Result< void > contains_sequence(std::string_view sequence, device_span< uint8_t > d_output, cuda::stream_ref stream=cudaStream_t{}) const
Queries all valid k-mers from a host-resident sequence into device memory.
Definition filter.cuh:486
float load_factor() const
Computes the fraction of set bits in the filter.
Definition filter.cuh:810
Result< FastxQueryReport > query_record_batch(RecordBatchView batch, Consumer &&consume, cuda::stream_ref stream=cudaStream_t{}) const
Queries a dense host-resident record batch and streams per-record results.
Definition filter.cuh:568
filter(const filter &)=delete
Non-copyable (owns device shard storage).
filter_ref< Config > ref() const noexcept
Non-owning device reference to this filter's shard storage.
Definition filter.cuh:137
Result< FastxQueryReport > query_fastx(std::istream &input, double fill_fraction=0.7, cuda::stream_ref stream=cudaStream_t{}) const
Queries all k-mers from a FASTA/FASTQ input stream via chunked streaming.
Definition filter.cuh:632
Result< void > contains_sequence_async(device_span< const char > d_sequence, device_span< uint8_t > d_output, cuda::stream_ref stream=cudaStream_t{}) const
Async query of k-mers from a device-resident sequence.
Definition filter.cuh:464
filter & operator=(const filter &)=delete
Result< void > contains_dense_packed_async(device_span< const uint64_t > d_words, uint64_t num_symbols, device_span< uint8_t > d_output, cuda::stream_ref stream=cudaStream_t{}) const
Async query of k-mers from a dense packed symbol buffer on the device.
Definition filter.cuh:248
Result< uint64_t > insert_dense_packed_async(device_span< const uint64_t > d_words, uint64_t num_symbols, cuda::stream_ref stream=cudaStream_t{})
Inserts all k-mers from a dense packed symbol buffer on the device.
Definition filter.cuh:207
#define CUSBF_TRY(expr)
Propagates a cusbf::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:246
#define CUSBF_CUDA_TRY(expr)
Propagates a CUDA error wrapped in cusbf::Result<void>.
Definition error.hpp:269
#define CUSBF_UNWRAP(expr)
Unwraps a cusbf::Result or throws std::runtime_error on failure (tests and apps).
Definition error.hpp:256
fastx_dispatch_path
How a FASTX file is read and chunked for GPU processing.
@ chunked_stream
Multiple GPU chunks, stream via istream (gzip or larger than RAM).
constexpr uint32_t kContainsSequenceStride
K-mers processed per query thread per inner loop iteration.
consteval bool separatorPositionAlwaysEncodesInvalid(char *input, uint64_t separatorPosition, uint64_t index)
Recursively tests whether placing the separator byte at any position in an input of valid bytes alway...
Definition Alphabet.cuh:37
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:219
constexpr uint64_t dense_packed_word_count(uint64_t num_symbols)
Returns the number of uint64_t words required for num_symbols encoded symbols.
Compile-time configuration for a cusbf::filter.
Definition config.cuh:35
static constexpr uint64_t symbolWidth
Input bytes per symbol.
Definition config.cuh:49
static constexpr uint64_t blockWordCount
64-bit words per 256-bit shard.
Definition config.cuh:62
static constexpr uint64_t cudaBlockSize
CUDA threads per kernel block.
Definition config.cuh:57
static constexpr uint16_t k
K-mer length in symbols.
Definition config.cuh:39
static constexpr uint64_t insertGroupSize
Threads cooperating on insert.
Definition config.cuh:68
static constexpr uint64_t queryGroupSize
Threads cooperating on query (fused path).
Definition config.cuh:70
static Error invalid_argument(std::string message)
Definition error.hpp:131
Summary statistics returned by Filter insert operations on FASTX and record-batch input.
Definition Fastx.hpp:151
uint64_t indexedBases
Total sequence bytes indexed.
Definition Fastx.hpp:155
uint64_t recordsIndexed
Records parsed and indexed.
Definition Fastx.hpp:153
uint64_t insertedKmers
K-mer windows inserted (valid symbols only).
Definition Fastx.hpp:157
Per-record metadata inside a normalized record batch.
Dense host-resident sequence batch plus explicit record boundaries.
Definition Fastx.hpp:32
std::string_view sequence
Raw record payloads concatenated.
Definition Fastx.hpp:34
Ordered non-overlapping byte range for one record inside a dense sequence batch.
Definition Fastx.hpp:24
Fallible API result: cuda::std::expected<T, Error> with cuSBF factories.
Definition error.hpp:152
A span that is assumed to point to device-accessible memory.
One 256-bit filter block stored as an array of Config::blockWordCount words.