39#include <system_error>
51 std::vector<std::filesystem::path>
paths;
58 [[nodiscard]] std::span<std::filesystem::path const>
mine() const noexcept {
66 if (status == MPI_SUCCESS)
return Ok();
67 char text[MPI_MAX_ERROR_STRING];
69 MPI_Error_string(status, text, &length);
76 int rank = 0, size = 1;
79 int const mine =
local ? size : rank;
81 CUDDL_TRY(
mpi_try(MPI_Allreduce(&mine, &failed, 1, MPI_INT, MPI_MIN, comm),
"MPI_Allreduce"));
83 if (failed == size)
return Ok();
89share_listing(std::filesystem::path
const& input,
int rank, MPI_Comm comm) {
92 if (rank != 0)
return Ok();
94 for (
auto const& path : paths) joined += path.string() +
'\n';
98 uint64_t length = joined.size();
99 CUDDL_TRY(
mpi_try(MPI_Bcast(&length, 1, MPI_UINT64_T, 0, comm),
"MPI_Bcast"));
100 joined.resize(length);
102 constexpr uint64_t chunk = uint64_t{1} << 30U;
103 for (uint64_t offset = 0; offset < length; offset += chunk) {
104 auto const count =
static_cast<int>(std::min(chunk, length - offset));
106 mpi_try(MPI_Bcast(joined.data() + offset,
count, MPI_CHAR, 0, comm),
"MPI_Bcast")
109 std::vector<std::filesystem::path> paths;
110 for (
size_t begin = 0; begin < joined.size();) {
111 auto const end = joined.find(
'\n', begin);
112 paths.emplace_back(joined.substr(begin, end - begin));
119[[nodiscard]]
inline size_t
121 auto const target =
static_cast<uint64_t
>(
static_cast<double>(
total) * rank / size);
124 while (i < bytes.size() && sum + bytes[i] / 2 < target) sum += bytes[i++];
132 std::vector<std::filesystem::path>
const& paths,
133 std::vector<uint16_t>
rows
136 file.blacklist_ = like.blacklist_;
137 file.metadata_ = like.metadata_;
139 file.names_.reserve(paths.size());
140 for (
auto const& path : paths) file.names_.push_back(path.string());
141 file.rows_ = std::move(
rows);
160 std::filesystem::path
const& input,
166 int rank = 0, size = 1;
171 auto const& paths = slice.
paths;
172 if (paths.size() >
static_cast<size_t>(std::numeric_limits<int>::max())) {
176 auto const share = [&](
int r) {
177 return paths.size() *
static_cast<size_t>(r) /
static_cast<size_t>(size);
179 auto const own = share(rank);
180 std::vector<uint64_t> mine(share(rank + 1) - own);
181 std::vector<std::error_code> errors(mine.size());
183 std::vector<std::jthread> threads;
184 for (
unsigned t = 0; t < workers && t < mine.size(); ++t) {
185 threads.emplace_back([&, t] {
186 for (
size_t i = t; i < mine.size(); i += workers) {
187 mine[i] = std::filesystem::file_size(paths[own + i], errors[i]);
193 for (
size_t i = 0; i < mine.size(); ++i) {
202 std::vector<int>
counts(size), offsets(size);
203 for (
int r = 0; r < size; ++r) {
204 offsets[r] =
static_cast<int>(share(r));
205 counts[r] =
static_cast<int>(share(r + 1) - share(r));
207 slice.
bytes.resize(paths.size());
212 static_cast<int>(mine.size()),
223 auto const total = std::accumulate(slice.
bytes.begin(), slice.
bytes.end(), uint64_t{0});
234[[nodiscard]]
inline Result<std::vector<uint32_t>>
236 auto const metadata = mine.
metadata();
238 CUDDL_TRY(
mpi_try(MPI_Bcast(&reference,
sizeof(reference), MPI_BYTE, 0, comm),
"MPI_Bcast"));
240 if (metadata.reference_count != slice.
last - slice.
first) {
243 if (!(reference == metadata.compatibility)) {
251 uint32_t
const count = metadata.reference_count;
252 std::vector<uint32_t>
counts(size);
254 MPI_Allgather(&
count, 1, MPI_UINT32_T,
counts.data(), 1, MPI_UINT32_T, comm),
257 if (std::accumulate(
counts.begin(),
counts.end(), uint64_t{0}) != slice.
paths.size()) {
265 MPI_Datatype
row = MPI_DATATYPE_NULL;
267 MPI_Type_contiguous(
static_cast<int>(buckets), MPI_UINT16_T, &
row),
"MPI_Type_contiguous"
284[[nodiscard]]
inline Result<reference_database_file>
288 std::exclusive_scan(sizes.begin(), sizes.end(), offsets.begin(), 0);
291 std::vector<uint16_t> rows(slice.
paths.size() * buckets);
328 : state_(std::move(other.state_)) {}
330 state_ = std::move(other.state_);
336 int provided = MPI_THREAD_SINGLE;
338 if (provided < MPI_THREAD_MULTIPLE) {
341 "shared_tile_counter needs MPI initialised with MPI_THREAD_MULTIPLE"
345 auto state = std::make_unique<shared_state>();
348 auto const bytes = state->rank == 0 ?
static_cast<MPI_Aint
>(
sizeof(uint64_t)) : 0;
352 bytes,
sizeof(uint64_t), MPI_INFO_NULL, comm, &state->count, &state->window
358 state->locked =
true;
359 if (state->rank == 0) {
363 detail::mpi_try(MPI_Comm_dup(MPI_COMM_SELF, &state->progress_comm),
"MPI_Comm_dup")
369 std::jthread([comm = state->progress_comm](
const std::stop_token& stop) {
370 while (!stop.stop_requested()) {
372 MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, comm, &flag, MPI_STATUS_IGNORE);
373 std::this_thread::yield();
380 counter.state_ = std::move(state);
386 std::lock_guard
const lock(state_->update);
387 uint64_t
const one = 1;
388 uint64_t previous = 0;
389 if (MPI_Fetch_and_op(&one, &previous, MPI_UINT64_T, 0, 0, MPI_SUM, state_->window) !=
391 MPI_Win_flush(0, state_->window) != MPI_SUCCESS) {
392 MPI_Abort(state_->comm, 1);
400 if (state_->rank == 0) {
401 uint64_t
const zero = 0;
405 &zero, 1, MPI_UINT64_T, 0, 0, 1, MPI_UINT64_T, MPI_REPLACE, state_->window
416 struct shared_state {
417 MPI_Comm comm = MPI_COMM_NULL;
418 MPI_Comm progress_comm = MPI_COMM_NULL;
419 MPI_Win window = MPI_WIN_NULL;
420 uint64_t* count =
nullptr;
424 std::jthread progress;
427 if (progress.joinable()) {
428 progress.request_stop();
432 MPI_Finalized(&finalized);
433 if (finalized)
return;
434 if (progress_comm != MPI_COMM_NULL) MPI_Comm_free(&progress_comm);
435 if (window == MPI_WIN_NULL)
return;
436 if (locked) MPI_Win_unlock_all(window);
437 MPI_Win_free(&window);
441 shared_tile_counter() =
default;
442 std::unique_ptr<shared_state> state_;
448template <
typename Search>
454 auto counter =
CUDDL_TRY(shared_tile_counter::make(comm));
457 .
shard = {.
index =
static_cast<uint32_t
>(rank), .
count =
static_cast<uint32_t
>(size)},
458 .next_tile = detail::ablated(
"tile-queue") ? std::function<uint64_t()>{}
459 : [&counter] {
return counter(); },
474template <u
int32_t K,
size_t BucketCount,
typename Layout,
typename OnTile>
481 return group.search_all_to_all(on_tile, std::move(split));
487template <u
int32_t K,
size_t BucketCount,
typename Layout,
typename OnTile>
495 return group.search_all_to_all(on_tile, options, std::move(split));
Host-owned reference sketches and labels, ready for binary storage or GPU upload.
std::span< uint16_t const > rows() const noexcept
Winner scores in reference-ID order, one bucket row per genome.
reference_database_metadata metadata() const noexcept
Compatibility metadata and reference count.
One reference database replicated on explicitly chosen devices, searched all-to-all by all of them at...
A tile counter shared by every rank of a communicator, for replica_search::next_tile.
__host__ ~shared_tile_counter()
static Result< shared_tile_counter > make(MPI_Comm comm)
__host__ shared_tile_counter(shared_tile_counter &&other) noexcept
uint64_t operator()()
The next tile number of the search, counting from 0 across every rank.
__host__ shared_tile_counter & operator=(shared_tile_counter &&other) noexcept
Result< void > reset()
Starts the count again at 0 once every rank has finished the previous search.
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
size_t slice_begin(std::span< uint64_t const > bytes, uint64_t total, int rank, int size)
First genome of rank rank's slice: contiguous slices of about equal bytes.
Result< std::vector< uint32_t > > slice_counts(reference_database_file const &mine, genome_slice const &slice, MPI_Comm comm)
Every rank's genome count, after checking collectively that mine holds the sketches of this rank's sl...
Result< void > agree(Result< void > local, MPI_Comm comm)
Returns local on the rank that failed, and on every other rank an error naming the lowest failing ran...
Result< std::vector< std::filesystem::path > > share_listing(std::filesystem::path const &input, int rank, MPI_Comm comm)
Rank 0's listing of input on every rank.
Result< void > shared_search(MPI_Comm comm, Search &&search)
Runs search with a replica_search that shares tiles across the ranks of comm.
Result< MPI_Datatype > row_type(uint32_t buckets)
One row of buckets scores as a single MPI element, so counts of rows stay within int.
constexpr bool ablated(std::string_view) noexcept
Always false: this build compiles the ablation switches out.
Result< void > mpi_try(int status, char const *call)
__global__ size_t uint16_t * rows
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< reference_database_file > gather_sketches(reference_database_file const &mine, genome_slice const &slice, MPI_Comm comm)
Gathers every rank's sketches of its genome_slice into one file on every rank.
Result< std::vector< std::filesystem::path > > list_genomes(std::filesystem::path const &input)
Paths from a directory (see genome_paths) or from a text file with one path per line,...
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
unsigned default_parser_workers()
Loaders a path build runs unless the caller asks for another count.
Result< void > search_all_to_all(replicated_database< K, BucketCount, Layout > &group, OnTile &&on_tile, MPI_Comm comm)
Compares every pair of group's references, sharing the tiles among the ranks of comm; collective.
Result< genome_slice > slice_genomes(std::filesystem::path const &input, MPI_Comm comm, unsigned workers=default_parser_workers())
Lists a genome collection once and splits it into one contiguous slice per rank of comm,...
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)
Builds a reference_database_file from gathered parts; the file's members are private.
static reference_database_file make(reference_database_file const &like, std::vector< std::filesystem::path > const &paths, std::vector< uint16_t > rows)
One rank's share of a genome collection split across an MPI communicator.
std::vector< std::filesystem::path > paths
Every genome of the collection in reference-ID order; the same on every rank.
size_t first
This rank's genomes are paths[first, last).
std::vector< uint64_t > bytes
Size of each genome's file in bytes.
std::span< std::filesystem::path const > mine() const noexcept
How a replicated_database search splits and delivers its tiles.
all_to_all_shard shard
This process's part of the pairs, when several processes share the search.
Matching-bucket threshold, independent of whether an index accelerates the search.