Raptor 4.0.0-rc.1
A fast and space-efficient pre-filter
Loading...
Searching...
No Matches
min_ibf_fpga_oneapi.hpp
1// SPDX-FileCopyrightText: 2006-2026 Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2026 Knut Reinert & MPI für molekulare Genetik
3// SPDX-FileCopyrightText: 2020-2025 Thomas Steinke & Zuse Institute Berlin
4// SPDX-License-Identifier: BSD-3-Clause
5
6#pragma once
7
8#if defined(FPGA_EMULATOR) == defined(FPGA_HARDWARE)
9# error "Either FPGA_EMULATOR or FPGA_HARDWARE have to be defined."
10#endif
11
12#include <dlfcn.h> // dlopen, dlsym
13#include <filesystem>
14#include <format>
15#include <string>
16
17#include <cereal/archives/binary.hpp>
18
19#include <raptor/index.hpp>
20
21#include <min_ibf_fpga/backend_sycl/exception_handler.hpp>
22#include <min_ibf_fpga/backend_sycl/shared.hpp>
23
24#include <sycl/ext/intel/fpga_extensions.hpp>
25#include <sycl/sycl.hpp>
26
27namespace raptor
28{
29
30template <size_t chunk_bits, bool profile = false>
32{
33 using HostSizeType = min_ibf_fpga::backend_sycl::HostSizeType;
34 using Chunk = ac_int<chunk_bits, false>;
35
36 static constexpr bool is_emulator =
37#ifdef FPGA_EMULATOR
38 true;
39#else
40 false;
41#endif
42
43private:
45 size_t technical_bins{}; // TODO: accessor?
46
47 size_t const minimalNumberOfMinimizers{};
48 size_t const maximalNumberOfMinimizers{};
49 sycl::queue utilitiesQueue;
50
51 std::function<void(void *)> utilities_QueueDeleter = [this](void * ptr)
52 {
53 sycl::free(ptr, utilitiesQueue);
54 };
55
56 template <typename T>
57 using unique_utilities_ptr = std::unique_ptr<T, decltype(utilities_QueueDeleter)>;
58
59 template <typename T>
60 unique_utilities_ptr<T> malloc_device_utilities(size_t const count)
61 {
62 return {sycl::malloc_device<T>(count, utilitiesQueue), utilities_QueueDeleter};
63 }
64
65 template <typename T>
66 unique_utilities_ptr<T> malloc_host_utilities(size_t const count)
67 {
68 return {sycl::malloc_host<T>(count, utilitiesQueue), utilities_QueueDeleter};
69 }
70
71 sycl::queue kernelQueue;
72 std::vector<size_t> thresholds_size_t;
73 size_t const bufferSizeBytes;
74 size_t const numberOfKernelCopys;
76 std::chrono::nanoseconds constructorDuration{};
77 std::array<sycl::event, 2> setupEvents; // 0: transferThresholds, 1: transferIBF
79 {
80 size_t numberOfQueries;
82
83 unique_utilities_ptr<char> queries_host;
85 unique_utilities_ptr<Chunk> results_host;
86
87 std::vector<sycl::event> kernelEvents;
88 };
89 std::array<buffer_data, 2> double_buffer;
90
91 void setup_fpga()
92 {
93 auto device_selector = []()
94 {
95 if constexpr (is_emulator)
96 return sycl::ext::intel::fpga_emulator_selector_v;
97 else
98 return sycl::ext::intel::fpga_selector_v;
99 }();
100
101 auto create_queue = [&device_selector]()
102 {
103 if constexpr (profile)
104 return sycl::queue(device_selector,
105 fpga_tools::exception_handler,
106 sycl::property::queue::enable_profiling());
107 else
108 return sycl::queue(device_selector, fpga_tools::exception_handler);
109 };
110
111 utilitiesQueue = create_queue();
112
113 sycl::device dev = utilitiesQueue.get_device();
114
115 if (!dev.has(sycl::aspect::usm_device_allocations) /*dev.get_info<info::device::usm_device_allocations>()*/)
116 {
117 std::cerr << "ERROR: The selected device does not support USM device allocations\n";
119 }
120 if (!dev.has(sycl::aspect::usm_host_allocations) /*dev.get_info<info::device::usm_host_allocations>()*/)
121 {
122 std::cerr << "ERROR: The selected device does not support USM host allocations\n";
124 }
125
126 kernelQueue = create_queue();
127 }
128
129public:
130 min_ibf_fpga_oneapi(std::filesystem::path const & index_path,
131 size_t minimalNumberOfMinimizers,
132 size_t maximalNumberOfMinimizers,
133 std::vector<size_t> thresholds,
134 uint8_t const bufferSizeMiB,
135 uint8_t const numberOfKernelCopys) :
136 minimalNumberOfMinimizers{minimalNumberOfMinimizers},
137 maximalNumberOfMinimizers{maximalNumberOfMinimizers},
138 thresholds_size_t{std::move(thresholds)},
139 bufferSizeBytes(bufferSizeMiB * 1'048'576),
140 numberOfKernelCopys{numberOfKernelCopys}
141 {
142
143 setup_fpga();
144 std::chrono::steady_clock::time_point start;
145
146 if constexpr (profile)
148
149 {
150 std::ifstream archiveStream{index_path, std::ios::binary};
151 cereal::BinaryInputArchive archive{archiveStream};
152 archive(index);
153 }
154
155 technical_bins = seqan::hibf::next_multiple_of_64(index.ibf().bin_count());
156
157 if constexpr (profile)
158 {
159 std::chrono::steady_clock::time_point const end = std::chrono::steady_clock::now();
160 constructorDuration = end - start;
161 }
162 }
163
164 size_t hash_function_count() const
165 {
166 return index.ibf().hash_function_count();
167 }
168
169 std::string get_library_name() const
170 {
171 return std::format("libraptor_search_fpga_kernel_w{}_k{}_b{}_kernels{}.so",
172 index.window_size(),
173 index.shape().size(),
174 technical_bins,
175 numberOfKernelCopys);
176 }
177
178 void count(std::filesystem::path const & query_path, std::filesystem::path const & output_path)
179 {
180 std::chrono::steady_clock::time_point countStart;
181 std::chrono::steady_clock::time_point hostStart;
182
183 if constexpr (profile)
184 countStart = std::chrono::steady_clock::now();
185
186 // Was (data.bit_size() + 63) / 64 * 64 / 8;
187 size_t const data_size_bytes = seqan::hibf::divide_and_ceil(index.ibf().bit_size(), 64u) * 8;
188 size_t const number_of_chunks = seqan::hibf::divide_and_ceil(data_size_bytes, sizeof(Chunk));
189 Chunk const * ibfData_host = reinterpret_cast<Chunk const *>(index.ibf().data());
190
191 auto ibfData_device = malloc_device_utilities<Chunk>(number_of_chunks);
192 setupEvents[1] = utilitiesQueue.copy(ibfData_host, ibfData_device.get(), number_of_chunks);
193
194 if constexpr (profile)
195 {
196 printDuration("IBF I/O:\t", countStart, constructorDuration);
197 hostStart = std::chrono::steady_clock::now();
198 }
199
200 // RunKernelType is a function pointer to a function that takes multiple arguments and returns void.
201 using RunKernelType = void (*)(sycl::queue &,
202 char const *,
203 HostSizeType const *,
204 HostSizeType const,
205 Chunk const *,
206 HostSizeType const,
207 HostSizeType const,
208 HostSizeType const,
209 HostSizeType const,
210 HostSizeType const *,
211 Chunk *,
213
214 std::string const library_name = get_library_name();
215 void * handle = dlopen(library_name.c_str(), RTLD_NOW);
216
217 if (!handle)
218 {
219 throw std::runtime_error{dlerror()};
220 }
221
222 dlerror(); // Clear any existing error
223
224 std::string const symbol_name = "RunKernel";
225 RunKernelType RunKernel = reinterpret_cast<RunKernelType>(dlsym(handle, symbol_name.c_str()));
226
227 char * error_message = dlerror();
228
229 if (error_message != NULL)
230 {
231 throw std::runtime_error{error_message};
232 }
233
234 static_assert(sizeof(HostSizeType) == sizeof(size_t));
235
236 auto thresholds_device = malloc_device_utilities<HostSizeType>(thresholds_size_t.size());
237 HostSizeType const * thresholds_host = reinterpret_cast<HostSizeType const *>(thresholds_size_t.data());
238 setupEvents[0] = utilitiesQueue.copy(thresholds_host, thresholds_device.get(), thresholds_size_t.size());
239
240 std::ofstream outputStream(output_path, std::ios::app);
241 std::string result_string{};
242
243 size_t const max_numberOfQueries = bufferSizeBytes / MIN_QUERY_LENGTH;
244 size_t const chunks_per_result = technical_bins / chunk_bits;
245
246 for (buffer_data & state : double_buffer)
247 {
248 state.queries_host = malloc_host_utilities<char>(bufferSizeBytes);
249 state.querySizes_host = malloc_host_utilities<HostSizeType>(max_numberOfQueries);
250 state.results_host = malloc_host_utilities<Chunk>(max_numberOfQueries * chunks_per_result);
251
252 state.kernelEvents.reserve(numberOfKernelCopys * 2 + 2); // +2: Distributor, Collector
253 state.numberOfQueries = 0;
254 }
255
256 auto const queueToFPGA = [&](buffer_data & state, size_t computeIteration)
257 {
258 if (computeIteration == 0)
259 {
260 utilitiesQueue.wait();
261 if constexpr (profile)
262 {
263 printDurationFromEvent("Thresh. Trans.:\t", setupEvents[0]);
264 printDurationFromEvent("IBF Transfer:\t", setupEvents[1]);
265 }
266 }
267
268 std::chrono::steady_clock::time_point start;
269
270 if constexpr (profile)
272
273 RunKernel(kernelQueue,
274 state.queries_host.get(),
275 state.querySizes_host.get(),
276 state.numberOfQueries,
277 ibfData_device.get(),
278 index.ibf().bin_size(),
279 std::countl_zero(index.ibf().bin_size()), // TODO: accessor index.ibf().hash_shift()
280 minimalNumberOfMinimizers,
281 maximalNumberOfMinimizers,
282 thresholds_device.get(),
283 state.results_host.get(),
284 state.kernelEvents);
285
286 if constexpr (profile)
287 printDuration("Queue:\t\t", start);
288 };
289
290 auto const waitOnFPGA = [&](buffer_data & state)
291 {
292 std::chrono::steady_clock::time_point start;
293
294 if constexpr (profile)
296
297 for (sycl::event e : state.kernelEvents)
298 e.wait();
299
300 if constexpr (profile)
301 printDuration("Wait:\t\t", start);
302 };
303
304 auto const outputResults = [&](buffer_data & state)
305 {
306 std::chrono::steady_clock::time_point start;
307
308 if constexpr (profile)
310
311 result_string.clear();
312
313 size_t const elements_per_result = technical_bins / 64;
314 size_t const elements_per_chunk = chunk_bits / 64;
315
316 if (elements_per_chunk * chunks_per_result != elements_per_result)
317 throw std::runtime_error("outputResults: elements_per_result/chunks_per_result mismatch");
318
319 for (size_t queryIndex = 0; queryIndex < state.ids.size(); queryIndex++)
320 {
321 // TODO: Use string view?
322 result_string += state.ids.at(queryIndex).substr(1, std::string::npos) + '\t';
323
324 for (size_t chunkOffset = 0; chunkOffset < chunks_per_result; ++chunkOffset)
325 {
326 Chunk & chunk = state.results_host.get()[queryIndex * chunks_per_result + chunkOffset];
327
328 for (size_t elementOffset = 0; elementOffset < elements_per_chunk; ++elementOffset)
329 {
330 uint64_t const & element = ((uint64_t *)&chunk)[elementOffset];
331
332 if (!element)
333 {
334 continue;
335 }
336
337 for (size_t byteOffset = 0; byteOffset < 8; ++byteOffset)
338 {
339 uint8_t const & value = ((uint8_t *)&element)[byteOffset];
340
341 if (!value)
342 {
343 continue;
344 }
345
346 uint8_t mask = 1;
347
348 for (size_t bitOffset = 0; bitOffset < 8; ++bitOffset)
349 {
350 if (value & mask)
351 {
352 result_string += std::to_string(chunkOffset * chunk_bits + elementOffset * 64
353 + byteOffset * 8 + bitOffset)
354 + ",";
355 }
356 mask <<= 1;
357 }
358 }
359 }
360 }
361 if (auto & last_char = result_string.back(); last_char == ',')
362 last_char = '\n';
363 else
364 result_string += '\n';
365 }
366
367 outputStream << result_string;
368
369 if constexpr (profile)
370 printDuration("Output:\t\t", start);
371
372 if constexpr (profile)
373 {
374 std::stringstream profilingOutput;
375
376 profilingOutput << "\n"
377 << "Kernels:\t" << getRuntime(state.kernelEvents) << " ms\n";
378
379 for (size_t kernelId = 0; kernelId < state.kernelEvents.size(); ++kernelId)
380 profilingOutput << "Kernel " << kernelId << ":\t" << getRuntime(state.kernelEvents[kernelId])
381 << " ms\n";
382
383 profilingOutput << "\n";
384
385 std::clog << profilingOutput.str();
386 }
387
388 state.kernelEvents.clear();
389 };
390
391 std::ifstream inputStream(query_path, std::ios::in | std::ios::binary);
392
393 std::string id;
394 std::string query;
395
396 size_t computeIteration = 0;
397 size_t currentBufferSize = 0;
398
399 buffer_data * currentBufferData = double_buffer.data();
400
401 while (std::getline(inputStream, id))
402 //for (auto && [id, query] : fin)
403 {
404 std::getline(inputStream, query);
405
406 if (currentBufferSize + query.size() > bufferSizeBytes)
407 {
408 if (currentBufferData->numberOfQueries == 0)
409 throw std::runtime_error("Buffer size to small");
410
411 if constexpr (profile)
412 printDuration("Host:\t\t", hostStart);
413
414 queueToFPGA(*currentBufferData, computeIteration);
415 ++computeIteration;
416 currentBufferData = std::addressof(double_buffer[computeIteration % 2]);
417
418 if (computeIteration >= 2)
419 {
420 waitOnFPGA(*currentBufferData);
421 outputResults(*currentBufferData); // could run async up to the next kernel start
422 }
423
424 if constexpr (profile)
425 hostStart = std::chrono::steady_clock::now();
426
427 currentBufferData->ids.clear();
428 currentBufferData->numberOfQueries = 0;
429
430 currentBufferSize = 0;
431 }
432
433 currentBufferData->ids.emplace_back(std::move(id));
434
435 currentBufferData->querySizes_host.get()[currentBufferData->numberOfQueries] = query.size();
436
437 // Copy query to queries
438 std::memcpy(currentBufferData->queries_host.get() + currentBufferSize, query.data(), query.size());
439
440 currentBufferSize += query.size();
441 currentBufferData->numberOfQueries++;
442
443 // Discard two lines from input stream (delimiter and quality)
444 for (size_t i = 0; i < 2; ++i)
445 inputStream.ignore(std::numeric_limits<std::streamsize>::max(), inputStream.widen('\n'));
446 }
447
448 // Handle left over queries
449 if (currentBufferData->numberOfQueries)
450 {
451 if constexpr (profile)
452 printDuration("Host:\t\t", hostStart);
453
454 queueToFPGA(*currentBufferData, computeIteration);
455 ++computeIteration;
456 }
457
458 // Wait for the remaining ones to finish
459 for (size_t i = std::min<size_t>(computeIteration, 2ul); i > 0; --i)
460 {
461 currentBufferData = std::addressof(double_buffer[(computeIteration + i) % 2]);
462 waitOnFPGA(*currentBufferData);
463 outputResults(*currentBufferData);
464 }
465
466 if constexpr (profile)
467 printDuration("Count total:\t", countStart);
468 }
469
470private:
471 static void printDuration(std::string const & label,
472 std::chrono::steady_clock::time_point const & start,
473 std::chrono::steady_clock::duration duration = std::chrono::steady_clock::duration())
474 {
475 auto const end = std::chrono::steady_clock::now();
476
477 duration += end - start;
478 auto const runtime = std::chrono::duration_cast<timeUnit>(duration).count();
479
480 std::stringstream profilingOutput;
481
482 profilingOutput << label << runtime << " ms\n";
483
484 std::clog << profilingOutput.str();
485 }
486
487 static void printDurationFromEvent(std::string const & label, sycl::event const event)
488 {
489 uint64_t const start = event.get_profiling_info<sycl::info::event_profiling::command_start>();
490 uint64_t const end = event.get_profiling_info<sycl::info::event_profiling::command_end>();
491
492 uint64_t const duration_ns = end - start;
493 float const duration_ms = static_cast<float>(duration_ns) / 1000000;
494
495 std::stringstream profilingOutput;
496
497 profilingOutput << label << duration_ms << " ms\n";
498
499 std::clog << profilingOutput.str();
500 }
501
502 // Helper for SYCL profiling info
503
504 static double getRuntime(std::vector<sycl::event> const events)
505 {
506 auto const comparator_start = [](sycl::event const & lhs, sycl::event const & rhs)
507 {
508 uint64_t lhsValue, rhsValue;
509
510 lhsValue = lhs.get_profiling_info<sycl::info::event_profiling::command_start>();
511 rhsValue = rhs.get_profiling_info<sycl::info::event_profiling::command_start>();
512
513 return lhsValue < rhsValue;
514 };
515
516 auto const comparator_end = [](sycl::event const & lhs, sycl::event const & rhs)
517 {
518 uint64_t lhsValue, rhsValue;
519
520 lhsValue = lhs.get_profiling_info<sycl::info::event_profiling::command_end>();
521 rhsValue = rhs.get_profiling_info<sycl::info::event_profiling::command_end>();
522
523 return lhsValue < rhsValue;
524 };
525
526 auto const startEvent = *std::min_element(events.begin(),
527 events.end(),
528 [&](auto const & lhs, auto const & rhs)
529 {
530 return comparator_start(lhs, rhs);
531 });
532 auto const endEvent = *std::max_element(events.begin(),
533 events.end(),
534 [&](auto const & lhs, auto const & rhs)
535 {
536 return comparator_end(lhs, rhs);
537 });
538
539 return getRuntime(startEvent, endEvent);
540 };
541
542 static double getRuntime(sycl::event const & event)
543 {
544 return getRuntime(event, event);
545 }
546
547 static double getRuntime(sycl::event const & startEvent, sycl::event const & endEvent)
548 {
549 uint64_t start, end;
550
551 start = startEvent.get_profiling_info<sycl::info::event_profiling::command_start>();
552 end = endEvent.get_profiling_info<sycl::info::event_profiling::command_end>();
553
554 std::chrono::nanoseconds const duration{end - start};
555
556 return std::chrono::duration_cast<timeUnit>(duration).count();
557 }
558};
559
560} // namespace raptor
T addressof(T... args)
T begin(T... args)
T c_str(T... args)
Definition min_ibf_fpga_oneapi.hpp:32
Definition index.hpp:48
T count(T... args)
T countl_zero(T... args)
T data(T... args)
T end(T... args)
T format(T... args)
T getline(T... args)
Provides raptor::raptor_index.
T max_element(T... args)
T memcpy(T... args)
T min_element(T... args)
T min(T... args)
T move(T... args)
T size(T... args)
T str(T... args)
Definition min_ibf_fpga_oneapi.hpp:79
T terminate(T... args)
T to_string(T... args)
Hide me