3#include <cuda_runtime.h>
4#include <cub/device/device_radix_sort.cuh>
5#include <cub/device/device_scan.cuh>
6#include <cub/device/device_segmented_sort.cuh>
7#include <cub/device/device_select.cuh>
8#include <cub/device/device_transform.cuh>
9#include <cuda/algorithm>
11#include <cuda/iterator>
12#include <cuda/memory_pool>
13#include <cuda/std/cstdint>
14#include <cuda/std/functional>
15#include <cuda/std/optional>
16#include <cuda/std/utility>
47template <u
int32_t K,
size_t BucketCount,
typename Layout = default_register_layout>
78 template <u
int32_t K,
size_t BucketCount,
typename Layout = default_register_layout>
80 static_assert(K <= std::numeric_limits<uint8_t>::max());
81 static_assert(BucketCount <= std::numeric_limits<uint16_t>::max());
84 .bucket_count = BucketCount,
85 .indexed_bucket_count = BucketCount,
86 .score_encoder_identity = 1U,
87 .exponent_bits = Layout::exponent_bits,
88 .mantissa_bits = Layout::mantissa_bits,
91 .canonicalisation_policy = 1U,
92 .blacklist_identity = 0U,
93 .blacklist_version = 0U,
162 [[nodiscard]] __host__ __device__
constexpr uint32_t
words_per_query() const noexcept {
167 [[nodiscard]] __host__ __device__ uint64_t
slot_count() const noexcept {
179 [[nodiscard]] __host__ __device__
bool
180 passed(uint32_t query_id, uint32_t reference_id)
const noexcept {
182 cuda::std::swap(query_id, reference_id);
184 if (query_id < first_query_id || query_id - first_query_id >=
query_count ||
189 return ((
pass_bits[word(query_id, reference_id)] >> (reference_id % 32U)) & 1U) != 0U;
194 [[nodiscard]] __host__ __device__ cuda::std::optional<batch_search_result>
195 find(uint32_t query_id, uint32_t reference_id)
const noexcept {
196 if (!
passed(query_id, reference_id))
return cuda::std::nullopt;
201 return result(query_id, reference_id,
results[position(query_id, reference_id)]);
206 [[nodiscard]] __host__ __device__ uint32_t
207 match_count(uint32_t query_id, uint32_t reference_id)
const noexcept {
209 cuda::std::swap(query_id, reference_id);
215 template <
typename F>
223 template <
typename F>
225 auto next = begin < end ? stored_before(begin) : 0U;
226 for (uint32_t query = begin; query < end; ++query) {
233 f(result(query_id, reference,
results[next++]));
238 for (uint32_t w = 0; w < words; ++w) {
239 for (
auto bits =
pass_bits[
static_cast<size_t>(query) * words + w]; bits != 0U;
241 auto const reference =
242 w * 32U +
static_cast<uint32_t
>(cuda::std::countr_zero(bits));
243 auto const at =
word_offsets !=
nullptr ? next++ : slot(query, reference);
244 f(result(query_id, reference,
results[at]));
252 [[nodiscard]] __host__ __device__ uint64_t
passing_before(uint32_t query)
const noexcept {
257 for (
size_t w = 0; w < words; ++w) {
258 total +=
static_cast<uint64_t
>(cuda::std::popcount(
pass_bits[w]));
264 [[nodiscard]] __host__ __device__ uint64_t
count() const noexcept {
267 if (words == 0U)
return 0U;
269 return static_cast<uint64_t
>(
word_offsets[words - 1U]) +
270 static_cast<uint64_t
>(cuda::std::popcount(
pass_bits[words - 1U]));
273 for (
size_t w = 0; w < words; ++w) {
274 total +=
static_cast<uint64_t
>(cuda::std::popcount(
pass_bits[w]));
284 .reference_id = reference_id,
289 [[nodiscard]] __host__ __device__
size_t
290 word(uint32_t query_id, uint32_t reference_id)
const noexcept {
295 [[nodiscard]] __host__ __device__ uint64_t
296 position(uint32_t query_id, uint32_t reference_id)
const noexcept {
298 auto const w = word(query_id, reference_id);
299 auto const below = (1U << (reference_id % 32U)) - 1U;
301 static_cast<uint64_t
>(cuda::std::popcount(
pass_bits[w] & below));
306 [[nodiscard]] __host__ __device__ uint64_t
307 slot(uint32_t query, uint32_t reference_id)
const noexcept {
317 [[nodiscard]] __host__ __device__ uint64_t stored_before(uint32_t query)
const noexcept {
340 detail::device_pinned_pool(stream.device(), detail::pinned_placement::device_node)
346 results_(stream, pool, 0,
cuda::no_init),
347 match_counts_(stream, pool, 0,
cuda::no_init),
348 pass_bits_(stream, pool, 0,
cuda::no_init),
349 word_offsets_(stream, pool, 0,
cuda::no_init) {}
355 : pool_(other.pool_),
356 results_(std::move(other.results_)),
357 match_counts_(std::move(other.match_counts_)),
358 pass_bits_(std::move(other.pass_bits_)),
359 word_offsets_(std::move(other.word_offsets_)),
360 count_(std::exchange(other.count_, 0U)),
361 has_match_counts_(other.has_match_counts_),
362 layout_(other.layout_) {}
370 [[nodiscard]] std::vector<batch_search_result>
passing()
const {
371 std::vector<batch_search_result> kept;
372 kept.reserve(
static_cast<size_t>(count_));
381 return {match_counts_.data(), has_match_counts_ ?
static_cast<size_t>(count_) : 0U};
388 view.
results = results_.data();
389 view.match_counts = has_match_counts_ ? match_counts_.data() :
nullptr;
390 view.pass_bits = layout_.
pass_bits ==
nullptr ? nullptr : pass_bits_.data();
391 view.word_offsets = layout_.
pass_bits ==
nullptr ? nullptr : word_offsets_.data();
397 template <typename T>
398 using pinned_buffer =
cuda::buffer<T,
cuda::mr::host_accessible,
cuda::mr::device_accessible>;
401 template <typename T>
402 void reserve(pinned_buffer<T>& buffer,
size_t count,
cuda::stream_ref stream) {
403 if (buffer.size() < count) buffer = pinned_buffer<T>(stream, pool_, count, cuda::no_init);
408 cuda::pinned_memory_pool_ref pool_;
409 pinned_buffer<packed_pairwise_counts> results_;
410 pinned_buffer<uint32_t> match_counts_;
411 pinned_buffer<uint32_t> pass_bits_;
412 pinned_buffer<uint32_t> word_offsets_;
414 bool has_match_counts_{};
415 batch_result_tile layout_{};
426[[nodiscard]]
inline Result<void>
430 host.layout_.word_offsets =
nullptr;
431 host.has_match_counts_ = tile.match_counts !=
nullptr;
432 if (tile.pass_bits ==
nullptr) {
433 auto const slots =
static_cast<size_t>(tile.slot_count());
435 host.reserve(host.results_, slots, stream);
440 cuda::std::span{tile.results, slots},
441 cuda::std::span{host.results_.data(), slots}
445 if (host.has_match_counts_) {
446 host.reserve(host.match_counts_, slots, stream);
451 cuda::std::span{tile.match_counts, slots},
452 cuda::std::span{host.match_counts_.data(), slots}
461 auto const words =
static_cast<size_t>(tile.query_count) * tile.words_per_query();
462 host.reserve(host.pass_bits_, words, stream);
463 host.reserve(host.word_offsets_, words, stream);
470 size_t scan_bytes = 0;
472 cub::DeviceScan::ExclusiveSum(
476 host.word_offsets_.data(),
477 static_cast<int64_t
>(words),
482 cuda::make_device_buffer<uint8_t>(stream, stream.device(), scan_bytes, cuda::no_init)
485 cub::DeviceScan::ExclusiveSum(
489 host.word_offsets_.data(),
490 static_cast<int64_t
>(words),
497 cuda::std::span{tile.pass_bits, words},
498 cuda::std::span{host.pass_bits_.data(), words}
502 host.count_ =
static_cast<uint64_t
>(host.word_offsets_.data()[words - 1U]) +
503 static_cast<uint64_t
>(cuda::std::popcount(host.pass_bits_.data()[words - 1U]));
504 auto const count =
static_cast<size_t>(host.count_);
505 host.reserve(host.results_, count, stream);
506 if (host.has_match_counts_) host.reserve(host.match_counts_, count, stream);
513 constexpr size_t staging_records =
size_t{1} << 23U;
514 auto const staged = std::min(count, staging_records);
516 cuda::make_device_buffer<packed_pairwise_counts>(
517 stream, stream.device(), staged, cuda::no_init
521 cuda::make_device_buffer<uint32_t>(
522 stream, stream.device(), tile.match_counts ==
nullptr ? 0U : staged, cuda::no_init
525 auto const kernel = tile.upper_triangle
526 ? detail::gather_passing_kernel<true, packed_pairwise_counts>
527 : detail::gather_passing_kernel<false, packed_pairwise_counts>;
528 auto const* offsets = host.word_offsets_.data();
529 for (
size_t first_word = 0U; first_word < words;) {
530 auto const base = offsets[first_word];
532 auto const last_word =
static_cast<size_t>(
533 std::upper_bound(offsets + first_word + 1U, offsets + words, base + staged - 32U) -
536 auto const end = last_word == words ? count :
static_cast<size_t>(offsets[last_word]);
545 host.word_offsets_.data(),
547 tile.reference_count,
552 tile.match_counts ==
nullptr ? nullptr : staging_matches.data()
555 auto const records = end - base;
560 cuda::std::span{staging.data(), records},
561 cuda::std::span{host.results_.data() + base, records}
564 if (tile.match_counts !=
nullptr) {
568 cuda::std::span{staging_matches.data(), records},
569 cuda::std::span{host.match_counts_.data() + base, records}
574 first_word = last_word;
592inline constexpr size_t visit_chunk_records =
size_t{1} << 20U;
599 size_t first_record{};
614[[nodiscard]] Result<void>
617 using pinned_counts = cuda::
618 buffer<packed_pairwise_counts, cuda::mr::host_accessible, cuda::mr::device_accessible>;
621 constexpr auto chunk_records = detail::visit_chunk_records;
622 auto const threshold = tile.pass_bits !=
nullptr;
623 auto const words =
static_cast<size_t>(tile.query_count) * tile.words_per_query();
626 std::vector<detail::visit_chunk> chunks;
627 std::vector<uint32_t> pass_bits;
628 std::vector<uint32_t> offsets;
629 cuda::device_buffer<uint32_t> device_offsets(
630 stream, cuda::device_default_memory_pool(stream.device())
634 detail::visit_chunk chunk{};
635 for (uint32_t query = 0; query < tile.query_count; ++query) {
636 auto const query_id = tile.first_query_id + query;
637 auto const row =
static_cast<size_t>(
639 ? (tile.reference_count > query_id + 1U ? tile.reference_count - query_id - 1U
641 : tile.reference_count
643 if (record + row - chunk.first_record > chunk_records && query != chunk.first) {
645 chunk.end_record = record;
646 chunks.push_back(chunk);
649 chunk.first_record = record;
653 chunk.last = tile.query_count;
654 chunk.end_record = record;
655 if (record != 0U) chunks.push_back(chunk);
656 }
else if (words != 0U) {
658 cuda::make_device_buffer<uint32_t>(stream, stream.device(), words, cuda::no_init)
660 auto const popcounts =
662 size_t scan_bytes = 0;
664 cub::DeviceScan::ExclusiveSum(
668 device_offsets.data(),
669 static_cast<int64_t
>(words),
674 cuda::make_device_buffer<uint8_t>(stream, stream.device(), scan_bytes, cuda::no_init)
677 cub::DeviceScan::ExclusiveSum(
681 device_offsets.data(),
682 static_cast<int64_t
>(words),
686 pass_bits.resize(words);
687 offsets.resize(words);
690 stream, cuda::std::span{tile.pass_bits, words}, cuda::std::span{pass_bits}
693 CUDDL_CUDA_TRY(cuda::copy_bytes(stream, device_offsets, cuda::std::span{offsets}));
695 auto const total =
static_cast<size_t>(offsets.back()) +
696 static_cast<size_t>(cuda::std::popcount(pass_bits.back()));
697 for (
size_t first = 0U; first < words;) {
698 auto const base =
static_cast<size_t>(offsets[first]);
700 auto const last =
static_cast<size_t>(
702 offsets.begin() +
static_cast<std::ptrdiff_t
>(first) + 1,
704 static_cast<uint32_t
>(base + chunk_records - 32U)
708 auto const end = last == words ? total :
static_cast<size_t>(offsets[last]);
709 if (end != base) chunks.push_back({first, last, base, end});
713 if (chunks.empty()) {
719 cuda::make_device_buffer<packed_pairwise_counts>(
720 stream, stream.device(), threshold ? chunk_records : 0U, cuda::no_init
723 std::array<pinned_counts, 2> buffers{
724 pinned_counts(stream, memory, chunk_records, cuda::no_init),
725 pinned_counts(stream, memory, chunk_records, cuda::no_init),
727 std::array<cuda::event, 2> ready{cuda::event(stream.device()), cuda::event(stream.device())};
729 auto const& chunk = chunks[k];
730 auto const records = chunk.end_record - chunk.first_record;
733 auto const kernel = tile.upper_triangle
734 ? detail::gather_passing_kernel<true, packed_pairwise_counts>
735 : detail::gather_passing_kernel<false, packed_pairwise_counts>;
744 device_offsets.data(),
746 tile.reference_count,
749 static_cast<uint32_t
>(chunk.first_record),
754 source = staging.data();
759 cuda::std::span{source, records},
760 cuda::std::span{buffers[k % 2U].data(), records}
763 ready[k % 2U].record(stream);
768 for (
size_t k = 0; k < chunks.size(); ++k) {
770 if (k + 1U < chunks.size())
CUDDL_TRY(enqueue(k + 1U));
772 auto const* packed = buffers[k % 2U].data();
773 auto const& chunk = chunks[k];
775 auto const emit = [&](uint32_t query_id, uint32_t reference_id) {
778 .reference_id = reference_id,
779 .counts = packed[next++].unpack(tile.bucket_count),
783 for (
auto query =
static_cast<uint32_t
>(chunk.first); query < chunk.last; ++query) {
784 auto const query_id = tile.first_query_id + query;
785 for (
auto reference = tile.upper_triangle ? query_id + 1U : 0U;
786 reference < tile.reference_count;
788 emit(query_id, reference);
793 auto const words_per_query = tile.words_per_query();
794 auto query =
static_cast<uint32_t
>(chunk.first / words_per_query);
795 auto column =
static_cast<uint32_t
>(chunk.first % words_per_query);
796 auto w = chunk.first;
797 while (w < chunk.last) {
799 auto const row_end = std::min(chunk.last, w + words_per_query - column);
800 auto const query_id = tile.first_query_id + query;
801 for (
auto base = column * 32U; w < row_end; ++w, base += 32U) {
802 for (
auto bits = pass_bits[w]; bits != 0U; bits &= bits - 1U) {
803 emit(query_id, base +
static_cast<uint32_t
>(cuda::std::countr_zero(bits)));
848 template <
typename Row>
850 auto const score =
static_cast<uint16_t
>(
row);
856 __host__ __device__ uint32_t
operator()(uint32_t i)
const noexcept {
862 __host__ __device__ int64_t
operator()(uint32_t bucket)
const noexcept {
867template <u
int32_t K,
size_t BucketCount,
typename Layout = default_register_layout>
891template <u
int32_t K,
size_t BucketCount,
typename Layout = default_register_layout>
895 CUDDL_TRY((validate_score_compatibility<K, BucketCount, Layout>(compatibility)));
899 if (compatibility.
key_mask != 0xffffU && compatibility.
key_mask != 0x7fffU) {
905template <u
int32_t K,
size_t BucketCount,
typename Layout = default_register_layout>
909 CUDDL_TRY((validate_score_compatibility<K, BucketCount, Layout>(compatibility)));
910 auto const full_bucket_count =
static_cast<uint32_t
>(BucketCount);
915 if (compatibility.
key_mask != 0xffffU && compatibility.
key_mask != 0x7fffU) {
924 return static_cast<uint64_t
>(compatibility.indexed_bucket_count) *
925 (
static_cast<uint64_t
>(compatibility.key_mask) + 1U);
927[[nodiscard]]
inline constexpr uint64_t
929 return static_cast<uint64_t
>(
reference_count) * compatibility.indexed_bucket_count;
940 compatibility.indexed_bucket_count *
sizeof(uint32_t);
944[[nodiscard]]
inline uintptr_t
align_up(uintptr_t address,
size_t alignment)
noexcept {
945 return (address + alignment - 1U) & ~(
static_cast<uintptr_t
>(alignment) - 1U);
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)));
976[[nodiscard]]
constexpr uint32_t
978 constexpr uint32_t pair_budget = 1U << 25U;
979 constexpr uint64_t tiles_per_part = 4U;
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));
995[[nodiscard]]
inline uint32_t
998 auto const budget = device.attribute(cuda::device_attributes::total_global_memory) / 4U;
999 auto const pairs = budget / bytes_per_pair;
1001 return static_cast<uint32_t
>(
1033 uint32_t searchers = 1U,
1034 std::function<uint64_t()> next_tile = {}
1036 if (shard.count == 0U || shard.index >= shard.count) {
1039 if (searchers == 0U || shard.count > std::numeric_limits<uint32_t>::max() / searchers) {
1044 queue.tile_queries_ =
1046 auto const count = queue.tile_queries_ == 0U
1049 for (uint32_t t = 0U; t <
count; ++t) {
1050 auto const lane = t % shard.count;
1051 auto const owner = (t / shard.count) % 2U == 0U ? lane : shard.
count - 1U -
lane;
1052 if (next_tile || owner == shard.index) queue.tiles_.push_back(t);
1054 queue.next_tile_ = std::move(next_tile);
1059 : tiles_(std::move(other.tiles_)),
1060 next_(other.next_.load()),
1061 next_tile_(std::move(other.next_tile_)),
1062 cancelled_(other.cancelled_.load()),
1063 tile_queries_(other.tile_queries_),
1064 reference_count_(other.reference_count_) {}
1067 [[nodiscard]] std::optional<tile>
claim() noexcept {
1068 if (cancelled_.load(std::memory_order_relaxed))
return std::nullopt;
1069 auto const i = next_tile_ ? next_tile_() : next_.fetch_add(1U, std::memory_order_relaxed);
1070 if (i >= tiles_.size())
return std::nullopt;
1071 auto const first = tiles_[i] * tile_queries_;
1072 return tile{first, std::min(tile_queries_, reference_count_ - first)};
1078 cancelled_.store(
true);
1083 return static_cast<bool>(next_tile_);
1088 return tile_queries_;
1092 [[nodiscard]]
size_t size() const noexcept {
1093 return tiles_.size();
1098 return reference_count_;
1102 [[nodiscard]]
size_t position(uint32_t first_query_id)
const noexcept {
1103 return static_cast<size_t>(
1104 std::lower_bound(tiles_.begin(), tiles_.end(), first_query_id / tile_queries_) -
1112 std::vector<uint32_t> tiles_;
1113 std::atomic<size_t> next_{0U};
1114 std::function<uint64_t()> next_tile_;
1115 std::atomic<bool> cancelled_{
false};
1116 uint32_t tile_queries_{};
1117 uint32_t reference_count_{};
1128template <u
int32_t K,
size_t BucketCount,
typename Layout = default_register_layout>
1130 static_assert(K >= 1 && K <= 31);
1131 static_assert(BucketCount >= (
size_t{1} << 11) && BucketCount <= (
size_t{1} << 13));
1132 static_assert((BucketCount & (BucketCount - 1)) == 0);
1145 bool indexed =
false,
1147 device_span<uint32_t const> key_directory = {},
1148 double index_pair_fraction = 0.0,
1149 device_span<uint32_t const> bitmap_cells = {},
1150 device_span<uint32_t const> posting_bitmaps = {},
1151 reference_delta_view deltas = {}
1155 index_offsets_(index_offsets),
1156 index_postings_(index_postings),
1157 index_keys_(index_keys),
1158 key_directory_(key_directory),
1159 bitmap_cells_(bitmap_cells),
1160 posting_bitmaps_(posting_bitmaps),
1162 index_pair_fraction_(index_pair_fraction),
1163 indexed_(indexed) {}
1166 [[nodiscard]] __host__ __device__
constexpr device_span<uint32_t const>
1193 return planes_.size_bytes();
1198 return index_offsets_.size_bytes() + index_postings_.size_bytes() +
1199 index_keys_.size_bytes();
1206 return reference_count == 0U ? 0U : query_plane_bytes(1U) + plane_alignment - 1U;
1227 cuda::stream_ref stream
1234 size_t selection_bytes = 0;
1235 auto const ids = cuda::make_counting_iterator(uint32_t{0});
1237 cub::DeviceSelect::If(
1241 static_cast<uint32_t*
>(
nullptr),
1242 static_cast<uint32_t*
>(
nullptr),
1244 detail::minimum_match_predicate{nullptr, 1U},
1249 return Err(selection.error());
1252 selection_bytes = std::max(
1258 constexpr size_t alignment_slack = plane_alignment - 1U + 3U + 255U;
1259 auto const arrays_bytes =
1260 query_plane_bytes(1U) +
1261 static_cast<size_t>(metadata_.
reference_count) * 2U *
sizeof(uint32_t);
1262 if (selection_bytes > std::numeric_limits<size_t>::max() - arrays_bytes - alignment_slack) {
1265 return arrays_bytes + alignment_slack + selection_bytes;
1271 auto const pair_count =
1273 return make_batch_requirements(
1274 pair_count, pair_count,
false, pair_count / reference_divisor()
1282 auto const pair_count =
1284 return make_batch_requirements(
1285 pair_count, pair_count,
true, pair_count / reference_divisor()
1291 auto const slot =
CUDDL_TRY(all_to_all_slot_pairs());
1292 if (slot > std::numeric_limits<uint32_t>::max() / 2U) {
1295 return make_batch_requirements(
1296 0U, pipeline_slots(all_to_all_tile_count()) * slot,
false, 0U
1307 auto const pair_count =
static_cast<uint64_t
>(
query_count) *
1310 return static_cast<uint32_t
>(pair_count * pipeline_slots(
tiles));
1317 auto requirements =
CUDDL_TRY(indexed_all_to_all_tile_search_requirements(
1321 requirements.maximum_pair_count =
1322 std::max(requirements.maximum_pair_count, exhaustive.maximum_pair_count);
1323 requirements.result_bytes = std::max(requirements.result_bytes, exhaustive.result_bytes);
1324 requirements.match_count_bytes =
1325 std::max(requirements.match_count_bytes, exhaustive.match_count_bytes);
1326 return requirements;
1335 cuda::stream_ref stream
1338 auto const expected_scores =
static_cast<size_t>(metadata_.
reference_count) * BucketCount;
1339 if (!rows_match_metadata(expected_scores)) {
1342 if (
query.size() != BucketCount ||
query.data() ==
nullptr) {
1346 (detail::validate_score_compatibility<K, BucketCount, Layout>(query_compatibility))
1360 if (
results.data() ==
nullptr) {
1364 if (workspace.data() ==
nullptr) {
1367 auto* query_planes =
CUDDL_TRY(stage_query_planes(
query, 0U, 1U, workspace, stream));
1368 detail::single_query_planes_kernel<BucketCount>
1380 return cuda_try(cudaGetLastError());
1391 cuda::stream_ref stream
1394 auto const expected_scores =
static_cast<size_t>(metadata_.
reference_count) * BucketCount;
1395 if (!rows_match_metadata(expected_scores)) {
1399 if (
query.size() != BucketCount ||
query.data() ==
nullptr) {
1403 (detail::validate_score_compatibility<K, BucketCount, Layout>(query_compatibility))
1414 if (result_count.empty()) {
1417 if (result_count.data() ==
nullptr) {
1426 return write_batch_result_count(metadata_.
reference_count, result_count, stream);
1429 return write_batch_result_count(0U, result_count, stream);
1433 if (workspace.size_bytes() < required_workspace) {
1436 if (workspace.data() ==
nullptr) {
1440 auto const workspace_begin =
reinterpret_cast<uintptr_t
>(workspace.data());
1441 auto const workspace_end = workspace_begin + workspace.size_bytes();
1442 if (workspace_end < workspace_begin) {
1445 auto* query_planes =
CUDDL_TRY(stage_query_planes(
query, 0U, 1U, workspace, stream));
1447 reinterpret_cast<uintptr_t
>(query_planes) + query_plane_bytes(1U),
alignof(uint32_t)
1449 auto*
match_counts =
reinterpret_cast<uint32_t*
>(address);
1450 address +=
static_cast<size_t>(metadata_.
reference_count) *
sizeof(uint32_t);
1453 address +=
static_cast<size_t>(metadata_.
reference_count) *
sizeof(uint32_t);
1455 if (address > workspace_end) {
1458 auto* selection_workspace =
reinterpret_cast<void*
>(address);
1459 auto selection_bytes =
static_cast<size_t>(workspace_end - address);
1462 detail::count_plane_matches_kernel<BucketCount>
1487 auto*
range_begin =
static_cast<uint32_t*
>(selection_workspace);
1489 detail::index_posting_ranges_kernel<<<
1495 index_offsets_.data(),
1496 index_keys_.empty() ? nullptr : index_keys_.data(),
1504 auto const counting_blocks =
1505 static_cast<uint32_t
>(
1506 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
1508 static_cast<uint32_t
>(stream.device().attribute(
1509 cuda::device_attributes::max_threads_per_multiprocessor
1512 detail::count_balanced_index_matches_kernel<<<
1520 index_postings_.data(),
1526 auto const ids = cuda::make_counting_iterator(uint32_t{0});
1528 cub::DeviceSelect::If(
1529 selection_workspace,
1533 result_count.data(),
1535 detail::minimum_match_predicate{match_counts, options.minimum_matches},
1540 detail::single_query_planes_kernel<BucketCount>
1549 result_count.data(),
1552 return cuda_try(cudaGetLastError());
1562 template <
typename OnTile>
1571 cuda::stream_ref stream
1578 CUDDL_TRY(validate_workspace(requirements, workspace));
1580 auto const storage_queries = external_batch_tile(
query_count, stream);
1581 auto const tile_size = storage_queries < 2U ? storage_queries : storage_queries / 2U;
1582 auto const tiles = tile_size == 0U ? 0U : (
query_count + tile_size - 1U) / tile_size;
1583 auto const slot =
static_cast<size_t>(tile_size) * metadata_.
reference_count;
1584 uint32_t next_tile = 0U;
1585 return pipeline_tiles(
1586 [&]() -> std::optional<uint32_t> {
1587 if (next_tile >=
tiles)
return std::nullopt;
1596 cuda::stream_ref
work) {
1598 return launch_batch_exhaustive<false>(
1622 template <
typename OnTile>
1632 cuda::stream_ref stream
1638 CUDDL_TRY(validate_indexed_batch_inputs(
1641 auto const tile_size = external_batch_tile(
query_count, stream);
1646 launch_batch_indexed<false>(
1647 queries.data() +
static_cast<size_t>(
first_query_id) * BucketCount,
1669 template <
typename OnTile>
1675 cuda::stream_ref stream,
1686 template <
typename OnTile>
1692 cuda::stream_ref stream,
1700 std::vector<all_to_all_tile_queue::tile> claimed;
1701 return pipeline_tiles(
1702 [&]() -> std::optional<uint32_t> {
1704 if (!
tile)
return std::nullopt;
1705 claimed.push_back(*
tile);
1706 return static_cast<uint32_t
>(claimed.size() - 1U);
1714 cuda::stream_ref
work) {
1715 auto const tile = claimed[index];
1716 return launch_batch_exhaustive<true>(
1718 tile.first_query_id,
1720 tile.first_query_id,
1740 template <
typename OnTile>
1747 cuda::stream_ref stream,
1754 std::forward<OnTile>(on_tile),
1764 template <
typename OnTile>
1771 cuda::stream_ref stream,
1778 CUDDL_TRY(validate_indexed_batch_inputs(
1784 std::optional<cuda::event> consumed;
1790 launch_batch_indexed<true>(
1792 tile->first_query_id,
1794 tile->first_query_id,
1802 if (!consumed) consumed.emplace(
CUDDL_CUDA_TRY(cuda::event(stream.device())));
1810 [[nodiscard]]
static constexpr uint32_t pipeline_slots(uint32_t
tiles)
noexcept {
1811 return tiles < 2U ? 1U : 2U;
1814 [[nodiscard]]
constexpr uint32_t all_to_all_tile_count() const noexcept {
1816 return tile_size == 0U ? 0U : (metadata_.
reference_count + tile_size - 1U) / tile_size;
1820 [[nodiscard]] Result<void> validate_tile_queue(all_to_all_tile_queue
const&
tiles)
const {
1828 [[nodiscard]] Result<uint32_t> all_to_all_slot_pairs()
const {
1829 return all_to_all_pair_count(
1848 template <
typename Next,
typename Launch,
typename OnTile>
1849 [[nodiscard]] Result<void> pipeline_tiles(
1852 device_span<batch_result_type>
results,
1856 cuda::stream_ref stream
1858 auto const slot_of = [&](uint32_t
tile) {
1859 auto const offset =
static_cast<size_t>(
tile % 2U) * slot;
1861 device_span<batch_result_type>{
results.data() + offset, slot},
1863 : device_span<uint32_t>{
match_counts.data() + offset, slot}
1866 auto const first = next();
1867 if (!first)
return Ok();
1868 auto ahead = next();
1870 auto const [slot_results, slot_matches] = slot_of(*first);
1871 on_tile(
CUDDL_TRY(launch(*first, slot_results, slot_matches, stream)));
1875 std::array<cuda::event, 2> searched{
1879 std::array<batch_result_tile, 2> pending{};
1880 auto const enqueue = [&](uint32_t
tile) -> Result<void> {
1883 auto const [slot_results, slot_matches] = slot_of(
tile);
1890 for (
auto tile = *first;; ++
tile) {
1891 bool const more = ahead.has_value();
1894 on_tile(pending[
tile % 2U]);
1895 if (!more)
return Ok();
1904 [[nodiscard]] Result<cuddl::batch_search_requirements>
1905 indexed_all_to_all_tile_search_requirements(
1911 auto const result_pair_count =
1913 return make_batch_requirements(dense_pair_count, result_pair_count,
true,
query_count);
1916 [[nodiscard]] Result<void> validate_stored_rows()
const {
1917 auto const expected_scores =
static_cast<size_t>(metadata_.
reference_count) * BucketCount;
1918 if (!rows_match_metadata(expected_scores)) {
1924 [[nodiscard]] Result<void> validate_index_storage()
const {
1925 CUDDL_TRY((detail::validate_indexed_score_compatibility<K, BucketCount, Layout>(
1928 if (!indexed_)
return Ok();
1930 auto const expected_postings =
static_cast<size_t>(
1933 auto const sparse = index_offsets_.empty();
1934 auto const valid_keys =
1935 sparse ? index_keys_.size() == expected_postings &&
1936 (expected_postings == 0U || index_keys_.data() !=
nullptr)
1937 : index_keys_.empty() &&
1938 index_offsets_.size() == static_cast<size_t>(cell_count + 1U) &&
1939 index_offsets_.data() != nullptr;
1940 if (!indexed_ || !valid_keys || index_postings_.size() != expected_postings ||
1941 (expected_postings != 0U && index_postings_.data() ==
nullptr)) {
1947 [[nodiscard]] uint32_t
1948 external_batch_tile(uint32_t
query_count, cuda::stream_ref stream)
const {
1956 [[nodiscard]] Result<uint32_t> dense_batch_pair_count(uint32_t
query_count)
const {
1958 if (pair_count > std::numeric_limits<uint32_t>::max()) {
1961 return static_cast<uint32_t
>(pair_count);
1964 [[nodiscard]] Result<uint32_t>
1975 uint64_t right = 2U *
static_cast<uint64_t
>(metadata_.
reference_count) -
1977 if ((left & 1U) == 0U) {
1982 auto const pair_count = left * right;
1983 if (pair_count > std::numeric_limits<uint32_t>::max()) {
1986 return static_cast<uint32_t
>(pair_count);
1990 static constexpr size_t plane_alignment = 16U;
1993 [[nodiscard]]
static constexpr size_t query_plane_bytes(uint32_t
query_count)
noexcept {
1994 return static_cast<size_t>(
query_count) * BucketCount *
sizeof(uint16_t);
1998 [[nodiscard]]
constexpr uint32_t reference_divisor() const noexcept {
2004 [[nodiscard]]
constexpr bool compares_planes() const noexcept {
2015 [[nodiscard]] Result<cuddl::batch_search_requirements> make_batch_requirements(
2016 uint32_t dense_pair_count,
2017 uint32_t maximum_pair_count,
2019 uint32_t staged_queries
2023 .result_bytes =
static_cast<size_t>(maximum_pair_count) *
sizeof(
batch_result_type),
2024 .match_count_bytes =
static_cast<size_t>(maximum_pair_count) *
sizeof(uint32_t),
2026 auto const staging_bytes = query_plane_bytes(staged_queries);
2027 if (!indexed || dense_pair_count == 0U) {
2029 if (maximum_pair_count != 0U) {
2030 requirements.counter_bytes =
sizeof(uint32_t);
2031 requirements.workspace_bytes =
2032 staging_bytes + plane_alignment - 1U +
sizeof(uint32_t);
2034 return requirements;
2037 auto const tile_query_count = dense_pair_count / reference_divisor();
2038 auto const word_count =
static_cast<size_t>(tile_query_count) *
2040 requirements.candidate_bytes =
word_count *
sizeof(uint32_t);
2041 if (!compares_planes()) {
2042 requirements.counter_bytes =
static_cast<size_t>(dense_pair_count) *
sizeof(uint32_t);
2043 if (!index_keys_.empty()) {
2045 requirements.temporary_bytes =
static_cast<size_t>(tile_query_count) *
2049 if (!bitmap_cells_.empty()) {
2050 requirements.temporary_bytes =
static_cast<size_t>(tile_query_count) *
2052 (
sizeof(uint2) +
sizeof(uint32_t)) +
2057 requirements.counter_bytes =
sizeof(uint32_t);
2059 constexpr size_t alignment_slack =
alignof(uint32_t) - 1U + plane_alignment - 1U + 255U;
2060 requirements.workspace_bytes = requirements.counter_bytes + requirements.candidate_bytes +
2061 staging_bytes + requirements.temporary_bytes +
2063 return requirements;
2066 [[nodiscard]]
static Result<void> validate_workspace(
2068 device_span<uint8_t> workspace
2073 if (requirements.
workspace_bytes != 0U && workspace.data() ==
nullptr) {
2081 [[nodiscard]] Result<uint32_t*> stage_query_planes(
2082 device_span<score_type const> queries,
2085 device_span<uint8_t> workspace,
2086 cuda::stream_ref stream
2088 auto const address =
2089 detail::align_up(
reinterpret_cast<uintptr_t
>(workspace.data()), plane_alignment);
2090 if (address + query_plane_bytes(
count) >
2091 reinterpret_cast<uintptr_t
>(workspace.data()) + workspace.size_bytes()) {
2094 auto*
planes =
reinterpret_cast<uint32_t*
>(address);
2095 detail::build_score_planes_kernel<BucketCount>
2099 stream.get()>>>(queries.data() + first_row * BucketCount,
count,
planes);
2104 [[nodiscard]] Result<uint32_t> validate_batch_queries(
2105 device_span<score_type const> queries,
2106 score_compatibility
const& query_compatibility,
2110 if (queries.size() % BucketCount != 0U) {
2113 if (!queries.empty() && queries.data() ==
nullptr) {
2116 auto const query_count = queries.size() / BucketCount;
2117 if (
query_count > std::numeric_limits<uint32_t>::max()) {
2124 if (
auto const validation =
2125 detail::validate_score_compatibility<K, BucketCount, Layout>(query_compatibility);
2127 return Err(validation.error());
2135 [[nodiscard]] Result<void> validate_batch_outputs(
2137 device_span<batch_result_type>
results,
2157 [[nodiscard]] Result<void> validate_indexed_batch_inputs(
2159 device_span<uint8_t> workspace,
2160 device_span<batch_result_type>
results,
2162 search_options options
2171 if (requirements.
workspace_bytes != 0U && workspace.data() ==
nullptr) {
2179 [[nodiscard]]
static Result<void> write_batch_result_count(
2181 device_span<uint32_t> result_count,
2182 cuda::stream_ref stream
2185 cub::DeviceTransform::Transform(
2186 cuda::make_constant_iterator(
count),
2187 result_count.data(),
2189 cuda::std::identity{},
2198 template <
bool AllToAll>
2199 [[nodiscard]] Result<batch_result_tile> launch_batch_exhaustive(
2200 device_span<score_type const> queries,
2201 uint32_t first_query,
2204 device_span<uint8_t> workspace,
2205 device_span<batch_result_type>
results,
2207 cuda::stream_ref stream
2209 uint32_t
const* query_planes =
nullptr;
2210 if constexpr (AllToAll) {
2211 query_planes = planes_.data() +
static_cast<size_t>(first_query) * (BucketCount / 2U);
2217 auto const counter_address =
2218 detail::align_up(
reinterpret_cast<uintptr_t
>(workspace.data()), plane_alignment) +
2220 if (counter_address +
sizeof(uint32_t) >
2221 reinterpret_cast<uintptr_t
>(workspace.data()) + workspace.size_bytes()) {
2224 auto*
cell_counter =
reinterpret_cast<uint32_t*
>(counter_address);
2228 CUDDL_TRY((launch_refine<detail::refine_candidates::reference_mask, AllToAll>(
2232 static_cast<uint32_t
>(BucketCount),
2243 detail::apply_reference_deltas_kernel<BucketCount, batch_result_type, AllToAll>;
2245 reinterpret_cast<void const*
>(kernel),
2246 cudaFuncAttributeMaxDynamicSharedMemorySize,
2247 static_cast<int>(detail::delta_correction_shared_bytes<BucketCount>)
2250 static_cast<uint32_t
>(
2251 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
2254 detail::delta_correction_shared_bytes<BucketCount>,
2266 CUDDL_TRY((launch_refine<detail::refine_candidates::all, AllToAll>(
2270 static_cast<uint32_t
>(BucketCount),
2287 [[nodiscard]]
static bool corrects_deltas(cuda::stream_ref stream) {
2288 return static_cast<size_t>(stream.device().attribute(
2289 cuda::device_attributes::max_shared_memory_per_block_optin
2290 )) >= detail::delta_correction_shared_bytes<BucketCount>;
2293 [[nodiscard]] batch_result_tile make_tile(
2294 device_span<batch_result_type>
results,
2308 .bucket_count =
static_cast<uint32_t
>(BucketCount),
2309 .upper_triangle = upper_triangle,
2316 template <detail::refine_cand
idates Cand
idates,
bool UpperTriangle>
2317 [[nodiscard]] Result<void> launch_refine(
2318 uint32_t
const* query_planes,
2324 device_span<batch_result_type>
results,
2326 cuda::stream_ref stream,
2335 detail::match_filter_supported<BucketCount>
2338 return launch_all_pairs<UpperTriangle>(
2350 auto const select_kernel = [&]<uint32_t Group>() {
2351 return counting ? detail::refine_batch_bitmap_kernel<
2358 : detail::refine_batch_bitmap_kernel<
2366 auto kernel = select_kernel.template operator()<detail::bitmap_refine_group<BucketCount>>();
2367 auto shared_bytes = detail::bitmap_refine_dynamic_bytes<BucketCount>;
2368 if constexpr (detail::bitmap_refine_staged<BucketCount>) {
2372 constexpr uint32_t hopper_group =
2373 cuda::std::min(8U, detail::bitmap_refine_group<BucketCount>);
2374 if (stream.device().attribute(cuda::device_attributes::compute_capability_major) == 9) {
2375 kernel = select_kernel.template operator()<hopper_group>();
2376 shared_bytes = hopper_group * BucketCount *
sizeof(uint16_t);
2380 reinterpret_cast<void const*
>(kernel),
2381 cudaFuncAttributeMaxDynamicSharedMemorySize,
2382 static_cast<int>(shared_bytes)
2384 int resident_blocks = 0;
2388 auto const multiprocessors =
static_cast<uint32_t
>(
2389 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
2392 multiprocessors*
static_cast<uint32_t
>(resident_blocks > 0 ? resident_blocks : 1),
2410 return cuda_try(cudaGetLastError());
2415 [[nodiscard]] Result<uint16_t const*> row_major_rows(cuda::stream_ref stream)
const {
2416 static std::mutex mutex;
2418 static auto*
const decoded =
new std::map<uint32_t const*, uint16_t*>();
2419 std::lock_guard lock(mutex);
2420 auto&
rows = (*decoded)[planes_.data()];
2421 if (
rows ==
nullptr) {
2424 detail::decode_score_planes_kernel<BucketCount>
2428 stream.get()>>>(planes_.data(),
count,
rows);
2437 template <
bool UpperTriangle>
2438 [[nodiscard]] Result<void> launch_all_pairs(
2439 uint32_t
const* query_planes,
2442 device_span<batch_result_type>
results,
2443 cuda::stream_ref stream,
2451 auto const kernel = naive ? detail::row_major_all_pairs_kernel<
2456 : detail::row_major_all_pairs_kernel<
2461 auto const multiprocessors =
static_cast<uint32_t
>(
2462 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
2472 return cuda_try(cudaGetLastError());
2475 constexpr auto group = detail::bitmap_refine_group<BucketCount>;
2476 constexpr auto shared_bytes = detail::bitmap_refine_dynamic_bytes<BucketCount>;
2478 detail::refine_all_pairs_kernel<BucketCount, batch_result_type, UpperTriangle, group>;
2480 reinterpret_cast<void const*
>(kernel),
2481 cudaFuncAttributeMaxDynamicSharedMemorySize,
2482 static_cast<int>(shared_bytes)
2484 auto const multiprocessors =
static_cast<uint32_t
>(
2485 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
2497 return cuda_try(cudaGetLastError());
2503 template <
bool UpperTriangle>
2504 [[nodiscard]] Result<void> launch_match_filter(
2505 uint32_t
const* query_planes,
2512 cuda::stream_ref stream
2514 constexpr auto group = detail::bitmap_refine_group<BucketCount>;
2515 constexpr auto shared_bytes = detail::bitmap_refine_dynamic_bytes<BucketCount>;
2516 auto const kernel = detail::match_filter_kernel<BucketCount, UpperTriangle, group>;
2518 reinterpret_cast<void const*
>(kernel),
2519 cudaFuncAttributeMaxDynamicSharedMemorySize,
2520 static_cast<int>(shared_bytes)
2522 int resident_blocks = 0;
2526 auto const multiprocessors =
static_cast<uint32_t
>(
2527 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
2530 multiprocessors*
static_cast<uint32_t
>(resident_blocks > 0 ? resident_blocks : 1),
2544 return cuda_try(cudaGetLastError());
2554 template <
bool AllToAll>
2555 [[nodiscard]] Result<batch_result_tile> launch_batch_indexed(
2557 uint32_t first_query,
2560 device_span<uint8_t> workspace,
2561 device_span<batch_result_type>
results,
2563 search_options options,
2564 cuda::stream_ref stream
2566 auto const requirements =
CUDDL_TRY(make_batch_requirements(
2569 auto const workspace_begin =
reinterpret_cast<uintptr_t
>(workspace.data());
2570 auto const workspace_end = workspace_begin + workspace.size_bytes();
2571 if (workspace_end < workspace_begin) {
2577 auto*
match_counts =
reinterpret_cast<uint32_t*
>(address);
2582 auto* staging =
reinterpret_cast<void*
>(address);
2588 auto* temporary_workspace =
reinterpret_cast<void*
>(address);
2589 constexpr uint32_t warp_width = 32;
2591 auto const multiprocessors =
static_cast<uint32_t
>(
2592 stream.device().attribute(cuda::device_attributes::multiprocessor_count)
2598 auto const result_tile = make_tile(
2607 uint16_t
const* query_scores = external_queries;
2608 uint32_t
const* query_planes =
nullptr;
2609 if constexpr (AllToAll) {
2610 query_planes = planes_.data() +
static_cast<size_t>(first_query) * (BucketCount / 2U);
2611 if (!compares_planes()) {
2612 auto* decoded =
static_cast<uint16_t*
>(staging);
2613 detail::decode_score_planes_kernel<BucketCount>
2615 static_cast<size_t>(
query_count) * (BucketCount / warp_width)
2619 stream.get()>>>(query_planes,
query_count, decoded);
2621 query_scores = decoded;
2624 auto* converted =
static_cast<uint32_t*
>(staging);
2625 detail::build_score_planes_kernel<BucketCount>
2627 static_cast<size_t>(
query_count) * (BucketCount / warp_width)
2631 stream.get()>>>(external_queries,
query_count, converted);
2633 query_planes = converted;
2636 auto const refine_selected = [&](
bool thresholded) -> Result<void> {
2638 return launch_refine<detail::refine_candidates::bitmap, AllToAll>(
2654 if (compares_planes()) {
2655 if constexpr (AllToAll) {
2664 if constexpr (detail::match_filter_supported<BucketCount>) {
2667 cudaMemsetAsync(
match_counts, 0,
sizeof(uint32_t), stream.get())
2669 CUDDL_TRY((launch_match_filter<AllToAll>(
2674 options.minimum_matches,
2684 CUDDL_TRY((launch_refine<detail::refine_candidates::all, AllToAll>(
2694 options.minimum_matches,
2700 auto const bits_from_counts = [&]() -> Result<void> {
2701 detail::candidate_bits_from_counts_kernel<<<
2709 options.minimum_matches,
2714 return cuda_try(cudaGetLastError());
2721 static_cast<uint32_t
>(stream.device().attribute(
2722 cuda::device_attributes::max_shared_memory_per_block_optin
2729 uint32_t counter_bits = 0U;
2730 if (!index_keys_.empty()) {
2731 ranges =
static_cast<uint2*
>(temporary_workspace);
2732 detail::sparse_batch_posting_ranges_kernel<BucketCount>
2738 key_directory_.data(),
2746 auto const hybrid = !bitmap_cells_.empty();
2747 uint32_t* starts =
nullptr;
2748 uint32_t*
counts =
nullptr;
2750 ranges =
static_cast<uint2*
>(temporary_workspace);
2752 starts =
reinterpret_cast<uint32_t*
>(
ranges + range_count);
2753 counts = starts + range_count;
2754 detail::hybrid_posting_ranges_kernel<BucketCount>
2757 index_offsets_.empty() ? nullptr : index_offsets_.data(),
2758 bitmap_cells_.data(),
2759 static_cast<uint32_t
>(bitmap_cells_.size()),
2768 counter_bits = std::max<uint32_t>(1U, std::bit_width(options.minimum_matches));
2770 auto const count_postings = [&]<uint32_t Bits>() -> Result<void> {
2772 reinterpret_cast<void const*
>(
2773 detail::count_batch_index_tile_kernel<BucketCount, Bits, uint16_t>
2775 cudaFuncAttributeMaxDynamicSharedMemorySize,
2776 static_cast<int>(
tile / 2U *
sizeof(uint32_t))
2778 detail::count_batch_index_tile_kernel<BucketCount, Bits>
2780 detail::index_tile_block_size,
2781 tile / 2U *
sizeof(uint32_t),
2785 index_offsets_.data(),
2786 index_postings_.data(),
2792 index_keys_.empty() ? nullptr : index_keys_.data(),
2794 options.minimum_matches,
2798 hybrid ? posting_bitmaps_.data() :
nullptr,
2802 return cuda_try(cudaGetLastError());
2804 switch (counter_bits) {
2806 CUDDL_TRY((count_postings.template
operator()<0>()));
2809 CUDDL_TRY((count_postings.template
operator()<1>()));
2812 CUDDL_TRY((count_postings.template
operator()<2>()));
2815 CUDDL_TRY((count_postings.template
operator()<3>()));
2818 CUDDL_TRY((count_postings.template
operator()<4>()));
2821 CUDDL_TRY((count_postings.template
operator()<5>()));
2824 CUDDL_TRY((count_postings.template
operator()<6>()));
2827 CUDDL_TRY((count_postings.template
operator()<7>()));
2830 CUDDL_TRY((count_postings.template
operator()<8>()));
2833 CUDDL_TRY((count_postings.template
operator()<9>()));
2836 CUDDL_TRY((count_postings.template
operator()<10>()));
2839 CUDDL_TRY((count_postings.template
operator()<11>()));
2842 CUDDL_TRY((count_postings.template
operator()<12>()));
2845 CUDDL_TRY((count_postings.template
operator()<13>()));
2848 CUDDL_TRY((count_postings.template
operator()<14>()));
2851 CUDDL_TRY((count_postings.template
operator()<15>()));
2854 CUDDL_TRY((count_postings.template
operator()<16>()));
2860 stream, cuda::std::span{
match_counts,
static_cast<size_t>(tile_pair_count)}, 0
2865 auto const bucket_blocks =
static_cast<uint32_t
>(
2866 std::min<size_t>((query_buckets + cells_per_block - 1U) / cells_per_block, 65535U)
2868 detail::count_batch_index_matches_kernel<BucketCount>
2873 index_offsets_.data(),
2874 index_postings_.data(),
2889 [[nodiscard]] __host__ __device__
constexpr bool rows_match_metadata(
2890 size_t expected_scores
2892 return planes_.size() * 2U == expected_scores &&
2893 (expected_scores == 0U || planes_.data() !=
nullptr);
2896 device_span<uint32_t const> planes_;
2897 reference_database_metadata metadata_;
2898 device_span<uint32_t const> index_offsets_;
2899 device_span<uint32_t const> index_postings_;
2900 device_span<uint16_t const> index_keys_;
2901 device_span<uint32_t const> key_directory_;
2902 device_span<uint32_t const> bitmap_cells_;
2903 device_span<uint32_t const> posting_bitmaps_;
2904 reference_delta_view deltas_;
2905 double index_pair_fraction_{};
2918template <u
int32_t K,
size_t BucketCount,
typename Layout = default_register_layout>
2921 template <u
int32_t,
size_t,
typename>
2924 static_assert(K >= 1 && K <= 31);
2925 static_assert(BucketCount >= (
size_t{1} << 11) && BucketCount <= (
size_t{1} << 13));
2926 static_assert((BucketCount & (BucketCount - 1)) == 0);
2941 : planes_(std::move(other.planes_)),
2942 device_(other.device_),
2943 metadata_(std::exchange(other.metadata_, {})),
2944 names_(std::move(other.names_)),
2945 identity_(std::move(other.identity_)),
2946 deltas_(std::move(other.deltas_)) {}
2949 if (
this != &other) {
2950 planes_ = std::move(other.planes_);
2951 device_ = other.device_;
2952 metadata_ = std::exchange(other.metadata_, {});
2953 names_ = std::move(other.names_);
2954 identity_ = std::move(other.identity_);
2955 deltas_ = std::move(other.deltas_);
2968 cuda::stream_ref stream
2973 database.metadata_ = {.compatibility = compatibility, .reference_count =
reference_count};
2974 if (!rows.empty()) {
2976 cuda::make_device_buffer<uint32_t>(
2977 stream, stream.device(), rows.size() / 2U, cuda::no_init
2980 detail::build_score_planes_kernel<BucketCount>
2985 stream.get()>>>(rows.data(),
reference_count, database.planes_.data());
2991 detail::build_reference_deltas<BucketCount>(
3004 auto const count =
static_cast<size_t>(metadata_.
reference_count) * BucketCount;
3005 if (scores.size() < count) {
3011 if (scores.data() ==
nullptr) {
3014 detail::decode_score_planes_kernel<BucketCount>
3020 stream.get()>>>(planes_.data(), metadata_.
reference_count, scores.data());
3021 return cuda_try(cudaGetLastError());
3025 [[nodiscard]] std::span<std::string const>
names() const noexcept {
3040 [[nodiscard]] cuda::device_ref
device() const noexcept {
3087 cuda::stream_ref stream
3089 return view().
search_async(query, query_compatibility, workspace, results, stream);
3092 template <
typename OnTile>
3096 uint32_t query_id_offset,
3101 cuda::stream_ref stream
3105 query_compatibility,
3109 std::forward<OnTile>(on_tile),
3110 result_match_counts,
3115 template <
typename OnTile>
3121 cuda::stream_ref stream,
3125 workspace, results, std::forward<OnTile>(on_tile), result_match_counts, stream, shard
3131 template <
typename OnTile>
3137 cuda::stream_ref stream,
3141 workspace, results, std::forward<OnTile>(on_tile), result_match_counts, stream, tiles
3146 cuda::stream_ref stream,
3149 auto search_view =
CUDDL_TRY(view(index));
3150 return search_view.indexed_single_query_workspace_bytes(stream);
3154 uint32_t query_count,
3155 cuda::stream_ref stream,
3158 auto search_view =
CUDDL_TRY(view(index));
3159 return search_view.indexed_batch_search_requirements(query_count, stream);
3165 auto search_view =
CUDDL_TRY(view(index));
3166 return search_view.indexed_all_to_all_search_requirements();
3176 cuda::stream_ref stream,
3179 auto search_view =
CUDDL_TRY(view(index));
3180 return search_view.search_indexed_async(
3181 query, query_compatibility, workspace, results, result_count, options, stream
3185 template <
typename OnTile>
3189 uint32_t query_id_offset,
3195 cuda::stream_ref stream,
3198 auto search_view =
CUDDL_TRY(view(index));
3199 return search_view.search_batch_indexed_async(
3201 query_compatibility,
3205 std::forward<OnTile>(on_tile),
3206 result_match_counts,
3212 template <
typename OnTile>
3219 cuda::stream_ref stream,
3223 auto search_view =
CUDDL_TRY(view(index));
3224 return search_view.search_all_to_all_indexed_async(
3227 std::forward<OnTile>(on_tile),
3228 result_match_counts,
3237 template <
typename OnTile>
3244 cuda::stream_ref stream,
3248 auto search_view =
CUDDL_TRY(view(index));
3249 return search_view.search_all_to_all_indexed_async(
3252 std::forward<OnTile>(on_tile),
3253 result_match_counts,
3263 [[nodiscard]] view_type view() const noexcept {
3265 {planes_.data(), planes_.size()},
3279 [[nodiscard]] Result<view_type> view(
3280 reference_index<K, BucketCount, Layout>
const* index
3282 if (index ==
nullptr)
return view();
3283 if (!identity_ || index->identity_ != identity_ || !index->indexed_) {
3286 auto const offset_count = index->index_offsets_.size();
3288 device_span<uint32_t const>{index->index_offsets_.data(), offset_count};
3289 auto const postings = device_span<uint32_t const>{
3290 index->index_postings_.data(), index->index_posting_capacity_
3293 {planes_.data(), planes_.size()},
3299 index->key_directory_,
3301 {index->bitmap_cells_.data(), index->bitmap_count_},
3302 index->posting_bitmaps_,
3307 [[nodiscard]] detail::reference_delta_view delta_view() const noexcept {
3308 return deltas_ ? deltas_->view() : detail::reference_delta_view{};
3312 : planes_(stream,
cuda::device_default_memory_pool(stream.
device())),
3313 device_(stream.
device()) {}
3315 [[nodiscard]]
static Result<uint32_t>
3316 validate_rows(device_span<score_type const> rows, score_compatibility compatibility) {
3318 (detail::validate_indexed_score_compatibility<K, BucketCount, Layout>(compatibility))
3320 if (
rows.size() % BucketCount != 0U) {
3323 if (!
rows.empty() &&
rows.data() ==
nullptr) {
3333 cuda::device_buffer<uint32_t> planes_;
3334 cuda::device_ref device_{0};
3335 reference_database_metadata metadata_{};
3336 std::vector<std::string> names_;
3338 std::shared_ptr<char const> identity_ = std::make_shared<char>(0);
3339 std::optional<detail::reference_deltas> deltas_;
Thread-safe source of the all-to-all tiles one process searches.
uint32_t tile_queries() const noexcept
Query rows per tile (the last tile may hold fewer).
static Result< all_to_all_tile_queue > make(uint32_t reference_count, all_to_all_shard shard={}, uint32_t searchers=1U, std::function< uint64_t()> next_tile={})
size_t size() const noexcept
Tiles this process owns, or every tile of the search when they are shared.
std::optional< tile > claim() noexcept
The next unclaimed tile, or nothing once all are claimed or after cancel.
void cancel() noexcept
Makes every later claim of this queue return nothing, for example after a device failed.
bool shared() const noexcept
Whether processes share the tiles through a next_tile counter.
size_t position(uint32_t first_query_id) const noexcept
Position, in claim order, of the tile starting at first_query_id.
all_to_all_tile_queue(all_to_all_tile_queue &&other) noexcept
uint32_t reference_count() const noexcept
Reference count the tiles cover.
Non-owning, trivially copyable view of one selected reference-row backing.
constexpr reference_database_metadata metadata() const noexcept
Compatibility metadata and reference count.
__host__ __device__ constexpr reference_database_view(device_span< uint32_t const > planes, reference_database_metadata metadata, device_span< uint32_t const > index_offsets={}, device_span< uint32_t const > index_postings={}, bool indexed=false, device_span< uint16_t const > index_keys={}, device_span< uint32_t const > key_directory={}, double index_pair_fraction=0.0, device_span< uint32_t const > bitmap_cells={}, device_span< uint32_t const > posting_bitmaps={}, reference_delta_view deltas={}) noexcept
Result< cuddl::batch_search_requirements > indexed_all_to_all_search_requirements() const
Storage for every unique database-row pair through the index, or with a threshold but no index; it al...
constexpr uint32_t single_query_result_count() const noexcept
Results one exhaustive query writes: one per reference.
Result< void > search_all_to_all_indexed_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, search_options options, cuda::stream_ref stream, all_to_all_shard shard={}) const
Indexed search of every unique database-row pair using bounded storage.
Result< cuddl::batch_search_requirements > all_to_all_search_requirements() const
Storage reused while exhaustively searching every unique database-row pair.
constexpr size_t persistent_index_bytes() const noexcept
Bytes this view's index needs for offsets, postings, and keys.
static constexpr uint32_t single_query_result_count(uint32_t reference_count) noexcept
Result< void > search_all_to_all_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, cuda::stream_ref stream, all_to_all_tile_queue &tiles) const
Exhaustive all-to-all search of the tiles claimed from tiles, which other searches,...
Result< cuddl::batch_search_requirements > batch_search_requirements(uint32_t query_count, cuda::stream_ref stream) const
Storage reused while exhaustively searching query_count compact rows.
static constexpr size_t persistent_row_bytes(uint32_t reference_count) noexcept
Bytes one compact row store needs for reference_count references.
static constexpr uint32_t all_to_all_result_capacity(uint32_t reference_count) noexcept
constexpr size_t persistent_row_bytes() const noexcept
Bytes this view's own row store needs.
constexpr uint32_t reference_count() const noexcept
Reference count.
Result< void > search_async(device_span< score_type const > query, score_compatibility const &query_compatibility, device_span< uint8_t > workspace, device_span< result_type > results, cuda::stream_ref stream) const
Compares one compact query row with every reference row on stream.
Result< cuddl::batch_search_requirements > indexed_batch_search_requirements(uint32_t query_count, cuda::stream_ref stream) const
Storage reused while searching query_count compact rows through the index.
Result< void > search_indexed_async(device_span< score_type const > query, score_compatibility const &query_compatibility, device_span< uint8_t > workspace, device_span< result_type > results, device_span< uint32_t > result_count, search_options options, cuda::stream_ref stream) const
Finds and exactly refines references meeting options on stream.
Result< void > search_all_to_all_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, cuda::stream_ref stream, all_to_all_shard shard={}) const
Exhaustively searches every unique database-row pair using bounded storage.
Result< size_t > indexed_single_query_workspace_bytes(cuda::stream_ref stream) const
Caller-owned bytes required by one positive-threshold indexed query.
constexpr size_t single_query_workspace_bytes() const noexcept
Caller-owned bytes for one exhaustive query on this view.
packed_pairwise_counts batch_result_type
static constexpr size_t single_query_workspace_bytes(uint32_t reference_count) noexcept
Caller-owned bytes for one exhaustive query: the query's bit-planes.
Result< void > search_batch_indexed_async(device_span< score_type const > queries, score_compatibility const &query_compatibility, uint32_t query_id_offset, device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, search_options options, cuda::stream_ref stream) const
Indexed search for compact queries using bounded reusable storage.
Result< void > search_all_to_all_indexed_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, search_options options, cuda::stream_ref stream, all_to_all_tile_queue &tiles) const
Indexed all-to-all search of the tiles claimed from tiles, which other searches may claim from concur...
__host__ __device__ constexpr device_span< uint32_t const > planes() const noexcept
Reference bit-plane rows (detail::score_plane_index layout), in reference-ID order.
constexpr bool has_index() const noexcept
True when an index backs this view.
Result< void > search_batch_async(device_span< score_type const > queries, score_compatibility const &query_compatibility, uint32_t query_id_offset, device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, cuda::stream_ref stream) const
Exhaustively searches compact queries using bounded reusable storage.
Host copy of a batch_result_tile holding only the passing results, packed in query-major,...
void tile() const &&=delete
friend Result< void > download(batch_result_tile const &, cuda::stream_ref, host_batch_results &)
Copies a device tile's passing results into host's pinned buffers, growing them only when needed,...
__host__ ~host_batch_results()
void passing_match_counts() const &&=delete
batch_result_tile tile() const &noexcept
View of the packed host results; valid while this object lives.
__host__ host_batch_results(host_batch_results &&other) noexcept
std::span< uint32_t const > passing_match_counts() const &noexcept
The passing results' index match counts, in passing order; empty unless match counts were requested.
host_batch_results(host_batch_results const &)=delete
std::vector< batch_search_result > passing() const
Every passing result, query-major and in reference order, as full values.
host_batch_results & operator=(host_batch_results const &)=delete
host_batch_results(cuda::stream_ref stream, cuda::pinned_memory_pool_ref pool)
Empty results to download into, with pinned buffers taken from pool.
host_batch_results(cuda::stream_ref stream)
Empty results to download into, with pinned buffers for stream's device.
Host-owned reference sketches and labels, ready for binary storage or GPU upload.
Move-only owner of one immutable contiguous reference database.
uint32_t reference_count() const noexcept
Reference count.
static constexpr size_t single_query_workspace_bytes(uint32_t reference_count) noexcept
size_t persistent_row_bytes() const noexcept
Bytes this database's own row store needs.
reference_database(reference_database &&other) noexcept
static constexpr uint32_t all_to_all_result_capacity(uint32_t reference_count) noexcept
Result< void > copy_scores_async(device_span< score_type > scores, cuda::stream_ref stream) const
Writes the reference scores, row-major in reference-ID order, to scores.
std::span< std::string const > names() const noexcept
Reference labels in reference-ID order.
static constexpr uint32_t single_query_result_count(uint32_t reference_count) noexcept
uint32_t single_query_result_count() const noexcept
Results one exhaustive query writes: one per reference.
Result< void > search_all_to_all_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, cuda::stream_ref stream, all_to_all_tile_queue &tiles) const
Exhaustive search of the tiles claimed from tiles; see all_to_all_tile_queue.
Result< size_t > search_workspace_bytes(cuda::stream_ref stream, reference_index< K, BucketCount, Layout > const *index=nullptr) const
Result< void > search_batch_async(device_span< score_type const > queries, score_compatibility const &query_compatibility, uint32_t query_id_offset, device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, cuda::stream_ref stream) const
reference_database_metadata metadata() const noexcept
Compatibility metadata and reference count.
Result< void > search_all_to_all_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, cuda::stream_ref stream, all_to_all_shard shard={}) const
reference_database & operator=(reference_database &&other) noexcept
reference_database & operator=(reference_database const &)=delete
Result< void > search_all_to_all_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, search_options options, cuda::stream_ref stream, reference_index< K, BucketCount, Layout > const *index, all_to_all_tile_queue &tiles) const
Indexed or threshold search of the tiles claimed from tiles; see all_to_all_tile_queue.
size_t single_query_workspace_bytes() const noexcept
Caller-owned bytes for one exhaustive query on this database; always zero.
reference_database(reference_database const &)=delete
Result< void > search_async(device_span< score_type const > query, score_compatibility const &query_compatibility, device_span< uint8_t > workspace, device_span< result_type > results, device_span< uint32_t > result_count, search_options options, cuda::stream_ref stream, reference_index< K, BucketCount, Layout > const *index=nullptr) const
static Result< reference_database > build_async(device_span< score_type const > rows, score_compatibility compatibility, cuda::stream_ref stream)
Builds a database from flat row-major scores on stream.
Result< cuddl::batch_search_requirements > all_to_all_search_requirements(reference_index< K, BucketCount, Layout > const *index=nullptr) const
Result< void > search_batch_async(device_span< score_type const > queries, score_compatibility const &query_compatibility, uint32_t query_id_offset, device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, search_options options, cuda::stream_ref stream, reference_index< K, BucketCount, Layout > const *index=nullptr) const
Result< cuddl::batch_search_requirements > batch_search_requirements(uint32_t query_count, cuda::stream_ref stream, reference_index< K, BucketCount, Layout > const *index=nullptr) const
cuda::device_ref device() const noexcept
Device holding the rows: the device of the stream that built them.
Result< void > search_async(device_span< score_type const > query, score_compatibility const &query_compatibility, device_span< uint8_t > workspace, device_span< result_type > results, cuda::stream_ref stream) const
__host__ ~reference_database()
static constexpr size_t persistent_row_bytes(uint32_t reference_count) noexcept
Bytes one compact row store needs for reference_count references.
Result< void > search_all_to_all_async(device_span< uint8_t > workspace, device_span< batch_result_type > results, OnTile &&on_tile, device_span< uint32_t > result_match_counts, search_options options, cuda::stream_ref stream, reference_index< K, BucketCount, Layout > const *index=nullptr, all_to_all_shard shard={}) const
Index-file persistence and the validation boundary for reference_index.
Move-only acceleration data for an immutable reference database.
#define CUDDL_CUDA_TRY(...)
Propagates a CUDA error wrapped in cuddl::Result<void>.
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
uint32_t warp_grid_blocks(size_t warps) noexcept
Blocks for a grid-stride launch covering warps warps of block_size threads.
constexpr uint32_t index_match_cells_per_warp
Cells one warp owns per iteration of count_batch_index_matches_kernel: one per lane.
__global__ uint32_t reference_count
__global__ size_t uint32_t bool blacklist_view bool Layout
__global__ uint32_t const uint32_t const * pass_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 * candidate_bits
constexpr uint32_t batch_query_tile_count
Minimum query rows per batch tile.
constexpr uint32_t match_filter_block_size
Threads per match_filter_kernel block.
__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_c...
constexpr uint32_t posting_range_block_size
Threads of the single index_posting_ranges_kernel block.
__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
static __global__ uint32_t const uint32_t uint32_t const uint32_t * match_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 minimum_matches
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.
__global__ uint32_t uint32_t indexed_bucket_count
@ reference_mask
Every pair of the layout whose reference is set in one reference-wide bitmap.
@ all
Every pair of the layout, or with pass bits requested, those meeting the threshold.
constexpr uint32_t all_pairs_block_size
Threads per refine_all_pairs_kernel block.
Result< void > validate_score_compatibility(score_compatibility const &compatibility)
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t key_mask
constexpr uint32_t delta_correction_warps
Warps per apply_reference_deltas_kernel block, and the most and fewest children one block cell takes.
constexpr uint32_t batch_query_tile_size(uint32_t reference_count, uint32_t query_count, uint32_t tile_limit=batch_query_tile_count) noexcept
@ device_node
The device's host NUMA node, where the system supports NUMA pools: for buffers the device writes,...
constexpr uint64_t seed
Domain separation constant folded into every packed k-mer before hashing.
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t uint32_t * range_end_scan
Result< void > validate_indexed_score_compatibility(score_compatibility const &compatibility)
constexpr uint32_t block_size
Threads per CTA for the single-pair and cardinality reduction kernels.
__global__ uint32_t uint32_t uint64_t uint32_t * ids
__global__ size_t uint32_t const uint32_t const uint32_t uint32_t uint16_t uint32_t tile
__global__ uint32_t uint32_t uint32_t const uint32_t uint32_t uint16_t uint32_t const SearchResult uint32_t * result_match_counts
constexpr uint32_t warps_per_block
__global__ uint32_t const uint32_t const uint32_t const uint32_t first_query_id
uintptr_t align_up(uintptr_t address, size_t alignment) noexcept
static __global__ uint32_t const uint16_t const uint32_t uint32_t uint16_t uint32_t * range_begin
constexpr double index_pair_fraction_limit
Index pair fraction (reference_index::pair_fraction) above which batch search compares bit-planes for...
Result< void > validate_non_indexed_score_compatibility(score_compatibility const &compatibility)
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.
__global__ size_t uint32_t query_count
__global__ uint32_t const uint32_t uint32_t const uint32_t const SearchResult * results
__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
@ hybrid
CPU inflation, followed by GPU FASTA normalisation of the inflated bytes.
constexpr uint64_t indexed_posting_count(uint32_t reference_count, score_compatibility const &compatibility) noexcept
::cuda::__ensure_current_context device_scope
Makes stream's device current for the enclosing scope and restores the previous context on exit.
constexpr bool ablations_enabled
__host__ __device__ constexpr uint32_t sparse_directory_entries(uint32_t reference_count) noexcept
Bounds per bucket in a sparse key directory.
constexpr uint64_t indexed_cell_count(score_compatibility const &compatibility) noexcept
__global__ uint16_t const uint32_t uint32_t uint32_t unsigned long long * work
__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 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.
constexpr bool ablated(std::string_view) noexcept
Always false: this build compiles the ablation switches out.
constexpr uint32_t bitmap_refine_block_size
Threads per refine_batch_bitmap_kernel block.
static __global__ uint32_t const * offsets
__global__ uint32_t uint32_t uint32_t SearchResult uint32_t const * reference_mask
uint32_t external_batch_query_limit(uint32_t reference_count, cuda::device_ref device)
Query rows one external batch tile may hold on device.
static __global__ uint32_t const uint32_t uint32_t const * postings
__host__ __device__ uint16_t score(uint64_t hash) noexcept
Computes the 16-bit DDL rarity score for hash.
__global__ uint32_t const uint32_t uint32_t const * candidate_ids
__global__ size_t uint16_t * rows
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 a...
cuda::pinned_memory_pool_ref device_pinned_pool(cuda::device_ref device, pinned_placement placement)
A page-locked memory pool accessible from device only.
Batch operations over contiguous device rows.
@ counts
One 24-byte batch_search_result per pair: query ID, reference ID and the lower, equal,...
Result< void > Ok() noexcept
Success return for Result<void>; same as return {}.
Result< void > download(batch_result_tile const &tile, cuda::stream_ref stream, host_batch_results &host)
Copies a device tile's passing results into host's pinned buffers, growing them only when needed,...
index_storage
Dense offsets favor query latency; sparse sorted keys reduce index memory and build time.
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
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.
Result< void > for_each_passing(batch_result_tile const &tile, cuda::stream_ref stream, F &&f)
Calls f on the host with every passing result of a device tile, query-major and in reference order; s...
static Error invalid_argument(std::string message)
static Error resource(std::string message)
Fallible API result: cuda::std::expected<T, Error> with cuDDL factories.
static Result ok(T value)
Part cuDDL of count disjoint parts of an all-to-all search.
Non-owning view of one batch-search tile's results.
packed_pairwise_counts const * results
__host__ __device__ uint32_t match_count(uint32_t query_id, uint32_t reference_id) const noexcept
The pair's index match count, in either order for all-to-all tiles; the pair must pass and counts mus...
uint32_t const * match_counts
Index match counts parallel to results, when requested; otherwise null.
__host__ __device__ constexpr uint32_t words_per_query() const noexcept
__host__ __device__ cuda::std::optional< batch_search_result > find(uint32_t query_id, uint32_t reference_id) const noexcept
The pair's result, or nothing when it did not pass.
__host__ __device__ bool passed(uint32_t query_id, uint32_t reference_id) const noexcept
Whether the pair has a result in this tile, in either order for all-to-all tiles.
uint32_t const * word_offsets
For packed results: the number of passing results before each pass-bit word.
uint32_t const * pass_bits
Passing pairs, when the tile applied a threshold; null when every slot is filled.
uint32_t bucket_count
Buckets per sketch, from which both-empty counts are recovered.
__host__ __device__ uint64_t passing_before(uint32_t query) const noexcept
Number of passing results in the tile's query rows before row query, which may be query_count.
__host__ __device__ uint64_t slot_count() const noexcept
Slots of the tile's layout, filled or not.
__host__ __device__ void for_each_passing(uint32_t begin, uint32_t end, F &&f) const
Calls f with the passing results of the tile's query rows [begin, end), in the order of the whole-til...
__host__ __device__ void for_each_passing(F &&f) const
Calls f with every passing result, query-major and in reference order.
__host__ __device__ uint64_t count() const noexcept
Number of passing results.
Caller-owned storage requirements for one bounded query tile.
friend bool operator==(batch_search_requirements const &, batch_search_requirements const &)=default
uint32_t maximum_pair_count
Stable query/reference IDs and their exact query-relative bucket comparison counts.
friend bool operator==(batch_search_result const &, batch_search_result const &)=default
uint32_t const * base_bits
One bit per reference in the candidate-bitmap layout, set for bases.
__host__ __device__ uint16_t operator()(Row row) const noexcept
__host__ __device__ uint32_t operator()(uint32_t i) const noexcept
__host__ __device__ int64_t operator()(uint32_t bucket) const noexcept
Popcount of one pass-bit word.
A span that is assumed to point to device-accessible memory.
One pair's exact bucket comparison counts in 64 bits, the storage and transfer form of batch-search r...
__host__ __device__ constexpr pairwise_counts unpack(uint32_t bucket_count) const noexcept
Raw per-bucket classification counts for one sketch pair comparison.
Stable reference ID and exact query-relative bucket comparison counts.
friend bool operator==(reference_search_result const &, reference_search_result const &)=default
Construction parameters of compatible score rows.
uint16_t indexed_bucket_count
uint64_t blacklist_identity
uint8_t canonicalisation_policy
uint8_t blacklist_version
uint32_t score_encoder_identity
friend bool operator==(score_compatibility const &, score_compatibility const &)=default
static constexpr score_compatibility current() noexcept
Metadata for score rows produced by the current cuDDL construction path.
Matching-bucket threshold, independent of whether an index accelerates the search.