diff --git a/include/aare/ClusterFinderCUDA.hpp b/include/aare/ClusterFinderCUDA.hpp index 81a528d..cb3d5cb 100644 --- a/include/aare/ClusterFinderCUDA.hpp +++ b/include/aare/ClusterFinderCUDA.hpp @@ -7,31 +7,21 @@ #include #include #include -#include #include #include namespace aare { -// Per-stream device resources +// Per-stream device resources (device-side only; all pinned host staging is +// class-level) template struct StreamContext { - cudaStream_t stream = nullptr; // handle to the stream + cudaStream_t stream = nullptr; FRAME_TYPE *d_frame = nullptr; PEDESTAL_TYPE *d_pd_mean = nullptr; PEDESTAL_TYPE *d_pd_sum = nullptr; PEDESTAL_TYPE *d_pd_sum2 = nullptr; - ClusterType *d_clusters = nullptr; - uint32_t *d_cluster_count = nullptr; - - // Pinned host staging buffers. These make cudaMemcpyAsync real async DMA - // transfers even when the caller's NDView points to pageable memory. - FRAME_TYPE *h_frame = nullptr; - uint32_t *h_cluster_count = nullptr; - ClusterType *h_clusters = nullptr; - - cudaEvent_t kernel_start = nullptr; - cudaEvent_t kernel_stop = nullptr; + uint8_t *d_output = nullptr; // [uint32_t count | ClusterType clusters[max]] }; template , @@ -46,15 +36,28 @@ class ClusterFinderCUDA { static constexpr int col_radius = ClusterType::cluster_size_x / 2; static constexpr int row_radius = ClusterType::cluster_size_y / 2; + size_t m_output_pinned_capacity = + 0; // # frames currently allocated in h_output_pinned + void *h_output_pinned = nullptr; + + // Pointer registered via cudaHostRegister by the caller (for pinned H2D + // speed). The class tracks it only to unregister in the destructor if the + // caller forgets. + void *m_registered_input = nullptr; + Shape<2> m_shape; size_t nrows; size_t ncols; size_t m_image_size; // nrows * ncols - int n_streams; - size_t m_capacity; - size_t m_image_bytes; - size_t m_cluster_bytes; + + int n_streams; + size_t m_max_clusters_per_frame; + + // Per-frame output layout helpers + size_t m_output_bytes_per_frame; // sizeof(uint32_t) + max * + // sizeof(ClusterType), aligned + size_t m_clusters_offset; // offset of cluster array within output block COMPUTE_TYPE m_nSigma; Pedestal m_pedestal; @@ -67,6 +70,11 @@ class ClusterFinderCUDA { float m_total_kernel_ms = 0.0f; size_t m_frames_processed = 0; + // Per-frame kernel timing event pool. Sized lazily to the largest batch + // seen; one event pair per frame slot. + std::vector m_kernel_start_pool; + std::vector m_kernel_stop_pool; + // Kernel parameters dim3 grid; dim3 block; @@ -79,30 +87,32 @@ class ClusterFinderCUDA { * @param m_image_size shape of the detector frame (rows, cols) * @param nSigma threshold in units of per-pixel pedestal * std - * @param capacity device-side cluster buffer size per stream + * @param max_clusters_per_frame tight upper bound on clusters/frame for + * fixed-size D2H * @param n_streams number of CUDA streams for multi-frame * overlap */ ClusterFinderCUDA(Shape<2> shape_, COMPUTE_TYPE nSigma = 5.0, - size_t capacity = 1000000, int n_streams_ = 1) + size_t max_clusters_per_frame = 2048, int n_streams_ = 5) : m_shape(shape_), nrows(shape_[0]), ncols(shape_[1]), m_image_size(nrows * ncols), n_streams(n_streams_), - m_capacity(capacity), m_nSigma(nSigma), - m_pedestal(shape_[0], shape_[1]), m_clusters(capacity) { + m_max_clusters_per_frame(max_clusters_per_frame), m_nSigma(nSigma), + m_pedestal(shape_[0], shape_[1]), m_clusters(max_clusters_per_frame) { if (n_streams_ <= 0) { throw std::invalid_argument( "ClusterFinderCUDA: n_streams must be > 0"); } - if (capacity > + if (max_clusters_per_frame > static_cast(std::numeric_limits::max())) { throw std::invalid_argument( - "ClusterFinderCUDA: capacity must fit in uint32_t"); + "ClusterFinderCUDA: max_clusters_per_frame must fit in " + "uint32_t"); } - if (capacity == 0) { + if (max_clusters_per_frame == 0) { throw std::invalid_argument( - "ClusterFinderCUDA: capacity must be > 0"); + "ClusterFinderCUDA: max_clusters_per_frame must be > 0"); } // Grid/Block dimensions @@ -118,15 +128,22 @@ class ClusterFinderCUDA { sizeof(COMPUTE_TYPE); m_image_bytes = m_image_size * sizeof(FRAME_TYPE); - m_cluster_bytes = m_capacity * sizeof(ClusterType); + + // Output block layout: [count][padding to ClusterType + // alignment][clusters] + constexpr size_t cluster_align = alignof(ClusterType); + const size_t count_bytes = sizeof(uint32_t); + // next multiple of cluster_align + m_clusters_offset = + (count_bytes + cluster_align - 1) & ~(cluster_align - 1); + m_output_bytes_per_frame = + m_clusters_offset + m_max_clusters_per_frame * sizeof(ClusterType); v_sc.resize(n_streams); for (int k = 0; k < n_streams; ++k) { auto &sc = v_sc[k]; CUDA_CHECK( cudaStreamCreateWithFlags(&sc.stream, cudaStreamNonBlocking)); - CUDA_CHECK(cudaEventCreate(&sc.kernel_start)); - CUDA_CHECK(cudaEventCreate(&sc.kernel_stop)); CUDA_CHECK(cudaMalloc(&sc.d_frame, m_image_bytes)); CUDA_CHECK(cudaMalloc(&sc.d_pd_mean, m_image_size * sizeof(PEDESTAL_TYPE))); @@ -134,19 +151,7 @@ class ClusterFinderCUDA { cudaMalloc(&sc.d_pd_sum, m_image_size * sizeof(PEDESTAL_TYPE))); CUDA_CHECK(cudaMalloc(&sc.d_pd_sum2, m_image_size * sizeof(PEDESTAL_TYPE))); - CUDA_CHECK(cudaMalloc(&sc.d_clusters, m_cluster_bytes)); - CUDA_CHECK(cudaMalloc(&sc.d_cluster_count, sizeof(uint32_t))); - - CUDA_CHECK(cudaMallocHost(reinterpret_cast(&sc.h_frame), - m_image_bytes)); - CUDA_CHECK( - cudaMallocHost(reinterpret_cast(&sc.h_cluster_count), - sizeof(uint32_t))); - if (m_cluster_bytes > 0) { - CUDA_CHECK( - cudaMallocHost(reinterpret_cast(&sc.h_clusters), - m_cluster_bytes)); - } + CUDA_CHECK(cudaMalloc(&sc.d_output, m_output_bytes_per_frame)); } } @@ -154,7 +159,6 @@ class ClusterFinderCUDA { for (auto &sc : v_sc) { if (sc.stream) cudaStreamSynchronize(sc.stream); - if (sc.d_frame) cudaFree(sc.d_frame); if (sc.d_pd_mean) @@ -163,25 +167,26 @@ class ClusterFinderCUDA { cudaFree(sc.d_pd_sum); if (sc.d_pd_sum2) cudaFree(sc.d_pd_sum2); - if (sc.d_clusters) - cudaFree(sc.d_clusters); - if (sc.d_cluster_count) - cudaFree(sc.d_cluster_count); - - if (sc.h_frame) - cudaFreeHost(sc.h_frame); - if (sc.h_clusters) - cudaFreeHost(sc.h_clusters); - if (sc.h_cluster_count) - cudaFreeHost(sc.h_cluster_count); - - if (sc.kernel_start) - cudaEventDestroy(sc.kernel_start); - if (sc.kernel_stop) - cudaEventDestroy(sc.kernel_stop); + if (sc.d_output) + cudaFree(sc.d_output); if (sc.stream) cudaStreamDestroy(sc.stream); } + + for (auto e : m_kernel_start_pool) + if (e) + cudaEventDestroy(e); + for (auto e : m_kernel_stop_pool) + if (e) + cudaEventDestroy(e); + m_kernel_start_pool.clear(); + m_kernel_stop_pool.clear(); + + // free pinned memory + if (h_output_pinned) + cudaFreeHost(h_output_pinned); + if (m_registered_input) + cudaHostUnregister(m_registered_input); } // Non-copyable, non-movable @@ -193,6 +198,30 @@ class ClusterFinderCUDA { void set_nSigma(COMPUTE_TYPE nSigma) { m_nSigma = nSigma; } COMPUTE_TYPE get_nSigma() const { return m_nSigma; } + /** + * @brief Pin an existing host buffer so that find_clusters_batched + * transfers it at full PCIe bandwidth (~22 GB/s) instead of going through + * the CUDA driver's internal staging (~15 GB/s for pageable memory). + * + * Call once before the processing loop (not per-frame). The buffer must + * cover the largest NDView you will pass to find_clusters_batched. + * Call unregister_input_buffer() when done, or the destructor will clean + * up. + */ + void register_input_buffer(void *ptr, size_t bytes) { + if (m_registered_input) + CUDA_CHECK(cudaHostUnregister(m_registered_input)); + CUDA_CHECK(cudaHostRegister(ptr, bytes, cudaHostRegisterDefault)); + m_registered_input = ptr; + } + + void unregister_input_buffer() { + if (m_registered_input) { + CUDA_CHECK(cudaHostUnregister(m_registered_input)); + m_registered_input = nullptr; + } + } + void push_pedestal_frame(NDView frame) { m_pedestal.push(frame); m_pedestal_dirty = true; @@ -221,61 +250,20 @@ class ClusterFinderCUDA { } /** - * @brief Find clusters in a single frame, appending them to the internal - * ClusterVector. + * @brief Find clusters in a single frame, appending results to the internal + * ClusterVector (accessible via steal_clusters). + * Delegates to find_clusters_batched to avoid duplicating the GPU + * pipeline. */ void find_clusters(NDView frame, uint64_t frame_number = 0) { - if (m_pedestal_dirty) { // need to update the pedestal on the gpu - sync_pedestal_to_device(); - m_pedestal_dirty = false; - } - - auto &sc = v_sc[0]; - const uint32_t n_pd_samples = - static_cast(m_pedestal.n_samples()); - - // First, CPU copies frame into a reusable pinned buffer - std::memcpy(sc.h_frame, frame.data(), m_image_bytes); - - // Reset cluster counter - CUDA_CHECK(cudaMemsetAsync(sc.d_cluster_count, 0, sizeof(uint32_t), - sc.stream)); - - // Upload frame - CUDA_CHECK(cudaMemcpyAsync(sc.d_frame, sc.h_frame, m_image_bytes, - cudaMemcpyHostToDevice, sc.stream)); - - // Timed Kernel launch - CUDA_CHECK(cudaEventRecord(sc.kernel_start, sc.stream)); - device::find_clusters_in_single_frame - <<>>( - sc.d_frame, sc.d_pd_mean, sc.d_pd_sum, sc.d_pd_sum2, - n_pd_samples, m_nSigma, nrows, ncols, sc.d_clusters, - sc.d_cluster_count, static_cast(m_capacity)); - CUDA_CHECK(cudaEventRecord(sc.kernel_stop, sc.stream)); - CUDA_CHECK(cudaGetLastError()); - - // Read back cluster count into pinned buffer - CUDA_CHECK(cudaMemcpyAsync(sc.h_cluster_count, sc.d_cluster_count, - sizeof(uint32_t), cudaMemcpyDeviceToHost, - sc.stream)); - - // Synchronize to ensure count is available before the CPU reads - // clusters - CUDA_CHECK(cudaStreamSynchronize(sc.stream)); - - record_kernel_time(sc); - - // Clamp to max in case of overflow - uint32_t n_found = *sc.h_cluster_count; - n_found = std::min(n_found, static_cast(m_capacity)); - - // Read back clusters + NDView batch( + frame.data(), + {1, static_cast(nrows), static_cast(ncols)}); + auto results = find_clusters_batched(batch, frame_number); m_clusters.set_frame_number(frame_number); - if (n_found > 0) { - append_device_clusters_to(m_clusters, sc, n_found); - } + auto &cv = results[0]; + for (size_t i = 0; i < cv.size(); ++i) + m_clusters.push_back(cv[i]); } /** @@ -293,80 +281,94 @@ class ClusterFinderCUDA { m_pedestal_dirty = false; } - const size_t n_frames = frames.shape(0); + const size_t n_frames_batch = frames.shape(0); const uint32_t n_pd_samples = static_cast(m_pedestal.n_samples()); + // Lazy grow D2H output staging buffer (one slot per frame) + if (n_frames_batch > m_output_pinned_capacity) { + if (h_output_pinned) + CUDA_CHECK(cudaFreeHost(h_output_pinned)); + CUDA_CHECK(cudaMallocHost( + &h_output_pinned, n_frames_batch * m_output_bytes_per_frame)); + m_output_pinned_capacity = n_frames_batch; + } + + ensure_event_pool(n_frames_batch); + std::vector> results; - results.reserve(n_frames); - for (size_t i = 0; i < n_frames; ++i) { + results.reserve(n_frames_batch); + for (size_t i = 0; i < n_frames_batch; ++i) { results.emplace_back(); results.back().set_frame_number(first_frame + i); } - const size_t n_rounds = (n_frames + n_streams - 1) / n_streams; - for (size_t round = 0; round < n_rounds; ++round) { + // Launch all frames round-robin across streams. + // If the caller has called register_input_buffer() on frames.data(), + // H2D runs at pinned DMA bandwidth (~22 GB/s); otherwise the CUDA + // driver stages it internally (~15 GB/s for pageable memory). + for (size_t frame_idx = 0; frame_idx < n_frames_batch; ++frame_idx) { + auto &sc = v_sc[frame_idx % n_streams]; - // Launch phase: fan out kernels on all streams for this round - for (int k = 0; k < n_streams; ++k) { - // OOB guard - const size_t frame_idx = round * n_streams + k; - if (frame_idx >= n_frames) - continue; + const FRAME_TYPE *h_src = frames.data() + frame_idx * m_image_size; + auto *d_cluster_count = reinterpret_cast(sc.d_output); - auto &sc_k = v_sc[k]; - const FRAME_TYPE *h_src = - frames.data() + frame_idx * m_image_size; + CUDA_CHECK(cudaMemsetAsync(d_cluster_count, 0, sizeof(uint32_t), + sc.stream)); + CUDA_CHECK(cudaMemcpyAsync(sc.d_frame, h_src, m_image_bytes, + cudaMemcpyHostToDevice, sc.stream)); - std::memcpy(sc_k.h_frame, h_src, m_image_bytes); + auto *d_clusters = reinterpret_cast( + sc.d_output + m_clusters_offset); + CUDA_CHECK( + cudaEventRecord(m_kernel_start_pool[frame_idx], sc.stream)); + device::find_clusters_in_single_frame + <<>>( + sc.d_frame, sc.d_pd_mean, sc.d_pd_sum, sc.d_pd_sum2, + n_pd_samples, m_nSigma, nrows, ncols, d_clusters, + d_cluster_count, + static_cast(m_max_clusters_per_frame)); + CUDA_CHECK( + cudaEventRecord(m_kernel_stop_pool[frame_idx], sc.stream)); + CUDA_CHECK(cudaGetLastError()); - CUDA_CHECK(cudaMemsetAsync(sc_k.d_cluster_count, 0, - sizeof(uint32_t), sc_k.stream)); - CUDA_CHECK( - cudaMemcpyAsync(sc_k.d_frame, sc_k.h_frame, m_image_bytes, - cudaMemcpyHostToDevice, sc_k.stream)); - - CUDA_CHECK(cudaEventRecord(sc_k.kernel_start, sc_k.stream)); - device::find_clusters_in_single_frame - <<>>( - sc_k.d_frame, sc_k.d_pd_mean, sc_k.d_pd_sum, - sc_k.d_pd_sum2, n_pd_samples, m_nSigma, nrows, ncols, - sc_k.d_clusters, sc_k.d_cluster_count, - static_cast(m_capacity)); - CUDA_CHECK(cudaEventRecord(sc_k.kernel_stop, sc_k.stream)); - CUDA_CHECK(cudaGetLastError()); - - // Queue count D2H immediately after the kernel - CUDA_CHECK(cudaMemcpyAsync( - sc_k.h_cluster_count, sc_k.d_cluster_count, - sizeof(uint32_t), cudaMemcpyDeviceToHost, sc_k.stream)); - } - - // Drain phase: fan in results from all streams - for (int k = 0; k < n_streams; ++k) { - const size_t frame_idx = round * n_streams + k; - if (frame_idx >= n_frames) - continue; - - auto &sc_k = v_sc[k]; - - // Wait for memset -> H2D -> kernel -> count D2H - CUDA_CHECK(cudaStreamSynchronize(sc_k.stream)); - - record_kernel_time(sc_k); - - uint32_t n_found = *sc_k.h_cluster_count; - n_found = std::min(n_found, - static_cast(m_capacity)); - - if (n_found > 0) { - append_device_clusters_to(results[frame_idx], sc_k, - n_found); - } - } + void *h_slot = static_cast(h_output_pinned) + + frame_idx * m_output_bytes_per_frame; + CUDA_CHECK(cudaMemcpyAsync(h_slot, sc.d_output, + m_output_bytes_per_frame, + cudaMemcpyDeviceToHost, sc.stream)); } + // Sync once per stream + const int streams_used = + std::min(n_streams, static_cast(n_frames_batch)); + for (int k = 0; k < streams_used; ++k) + CUDA_CHECK(cudaStreamSynchronize(v_sc[k].stream)); + + // Drain: fan in results from pinned D2H output pool + for (size_t frame_idx = 0; frame_idx < n_frames_batch; ++frame_idx) { + const void *h_slot = static_cast(h_output_pinned) + + frame_idx * m_output_bytes_per_frame; + uint32_t n_found = *reinterpret_cast(h_slot); + n_found = std::min( + n_found, static_cast(m_max_clusters_per_frame)); + + if (n_found > 0) { + const auto *src = reinterpret_cast( + static_cast(h_slot) + m_clusters_offset); + for (uint32_t i = 0; i < n_found; ++i) + results[frame_idx].push_back(src[i]); + } + + float kernel_ms = 0.0f; + CUDA_CHECK(cudaEventElapsedTime(&kernel_ms, + m_kernel_start_pool[frame_idx], + m_kernel_stop_pool[frame_idx])); + m_total_kernel_ms += kernel_ms; + } + + m_frames_processed += n_frames_batch; return results; } @@ -407,28 +409,16 @@ class ClusterFinderCUDA { CUDA_CHECK(cudaStreamSynchronize(sc.stream)); } - /** - * Copy n_found clusters from sc.d_clusters into the given ClusterVector - * and block on the transfer. - */ - void append_device_clusters_to(ClusterVector &cv, SC &sc, - uint32_t n_found) { - - CUDA_CHECK(cudaMemcpyAsync(sc.h_clusters, sc.d_clusters, - n_found * sizeof(ClusterType), - cudaMemcpyDeviceToHost, sc.stream)); - - CUDA_CHECK(cudaStreamSynchronize(sc.stream)); - - for (uint32_t i = 0; i < n_found; ++i) - cv.push_back(sc.h_clusters[i]); - } - - void record_kernel_time(SC &sc) { - float ms = 0.0f; - CUDA_CHECK(cudaEventElapsedTime(&ms, sc.kernel_start, sc.kernel_stop)); - m_total_kernel_ms += ms; - m_frames_processed++; + void ensure_event_pool(size_t n_frames) { + const size_t old_size = m_kernel_start_pool.size(); + if (n_frames <= old_size) + return; + m_kernel_start_pool.resize(n_frames); + m_kernel_stop_pool.resize(n_frames); + for (size_t i = old_size; i < n_frames; ++i) { + CUDA_CHECK(cudaEventCreate(&m_kernel_start_pool[i])); + CUDA_CHECK(cudaEventCreate(&m_kernel_stop_pool[i])); + } } }; diff --git a/include/aare/ClusterFinderCUDA_old.hpp b/include/aare/ClusterFinderCUDA_old.hpp new file mode 100644 index 0000000..81a528d --- /dev/null +++ b/include/aare/ClusterFinderCUDA_old.hpp @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: MPL-2.0 +#pragma once +#include "aare/ClusterFinder.hpp" +#include "aare/clusterfinder_kernel.cuh" +#include "aare/utils/cuda_check.cuh" +#include +#include +#include +#include +#include +#include +#include + +namespace aare { + +// Per-stream device resources +template +struct StreamContext { + cudaStream_t stream = nullptr; // handle to the stream + FRAME_TYPE *d_frame = nullptr; + PEDESTAL_TYPE *d_pd_mean = nullptr; + PEDESTAL_TYPE *d_pd_sum = nullptr; + PEDESTAL_TYPE *d_pd_sum2 = nullptr; + ClusterType *d_clusters = nullptr; + uint32_t *d_cluster_count = nullptr; + + // Pinned host staging buffers. These make cudaMemcpyAsync real async DMA + // transfers even when the caller's NDView points to pageable memory. + FRAME_TYPE *h_frame = nullptr; + uint32_t *h_cluster_count = nullptr; + ClusterType *h_clusters = nullptr; + + cudaEvent_t kernel_start = nullptr; + cudaEvent_t kernel_stop = nullptr; +}; + +template , + typename FRAME_TYPE = uint16_t, typename PEDESTAL_TYPE = double, + typename = std::enable_if_t::value>> +class ClusterFinderCUDA { + using COMPUTE_TYPE = + device::COMPUTE_TYPE; // match the kernel's internal precision + + static constexpr int BLOCK_X = 16; + static constexpr int BLOCK_Y = 16; + static constexpr int col_radius = ClusterType::cluster_size_x / 2; + static constexpr int row_radius = ClusterType::cluster_size_y / 2; + + Shape<2> m_shape; + size_t nrows; + size_t ncols; + size_t m_image_size; // nrows * ncols + int n_streams; + size_t m_capacity; + + size_t m_image_bytes; + size_t m_cluster_bytes; + + COMPUTE_TYPE m_nSigma; + Pedestal m_pedestal; + ClusterVector m_clusters; + bool m_pedestal_dirty = true; + + using SC = StreamContext; + std::vector v_sc; + + float m_total_kernel_ms = 0.0f; + size_t m_frames_processed = 0; + + // Kernel parameters + dim3 grid; + dim3 block; + size_t shmem_bytes; + + public: + /** + * @brief Construct a ClusterFinderCUDA + * + * @param m_image_size shape of the detector frame (rows, cols) + * @param nSigma threshold in units of per-pixel pedestal + * std + * @param capacity device-side cluster buffer size per stream + * @param n_streams number of CUDA streams for multi-frame + * overlap + */ + ClusterFinderCUDA(Shape<2> shape_, COMPUTE_TYPE nSigma = 5.0, + size_t capacity = 1000000, int n_streams_ = 1) + : m_shape(shape_), nrows(shape_[0]), ncols(shape_[1]), + m_image_size(nrows * ncols), n_streams(n_streams_), + m_capacity(capacity), m_nSigma(nSigma), + m_pedestal(shape_[0], shape_[1]), m_clusters(capacity) { + if (n_streams_ <= 0) { + throw std::invalid_argument( + "ClusterFinderCUDA: n_streams must be > 0"); + } + + if (capacity > + static_cast(std::numeric_limits::max())) { + throw std::invalid_argument( + "ClusterFinderCUDA: capacity must fit in uint32_t"); + } + + if (capacity == 0) { + throw std::invalid_argument( + "ClusterFinderCUDA: capacity must be > 0"); + } + + // Grid/Block dimensions + block = dim3(BLOCK_X, BLOCK_Y); + grid = dim3((static_cast(ncols) + BLOCK_X - 1) / BLOCK_X, + (static_cast(nrows) + BLOCK_Y - 1) / BLOCK_Y); + + // Shared memory: one tile of (BLOCK_X + 2*col_radius) x (BLOCK_Y + + // 2*row_radius) elements + // Mixed precision used -> shmem takes COMPUTE_TYPE = floats (not + // PEDESTAL_TYPE) + shmem_bytes = (BLOCK_X + 2 * col_radius) * (BLOCK_Y + 2 * row_radius) * + sizeof(COMPUTE_TYPE); + + m_image_bytes = m_image_size * sizeof(FRAME_TYPE); + m_cluster_bytes = m_capacity * sizeof(ClusterType); + + v_sc.resize(n_streams); + for (int k = 0; k < n_streams; ++k) { + auto &sc = v_sc[k]; + CUDA_CHECK( + cudaStreamCreateWithFlags(&sc.stream, cudaStreamNonBlocking)); + CUDA_CHECK(cudaEventCreate(&sc.kernel_start)); + CUDA_CHECK(cudaEventCreate(&sc.kernel_stop)); + CUDA_CHECK(cudaMalloc(&sc.d_frame, m_image_bytes)); + CUDA_CHECK(cudaMalloc(&sc.d_pd_mean, + m_image_size * sizeof(PEDESTAL_TYPE))); + CUDA_CHECK( + cudaMalloc(&sc.d_pd_sum, m_image_size * sizeof(PEDESTAL_TYPE))); + CUDA_CHECK(cudaMalloc(&sc.d_pd_sum2, + m_image_size * sizeof(PEDESTAL_TYPE))); + CUDA_CHECK(cudaMalloc(&sc.d_clusters, m_cluster_bytes)); + CUDA_CHECK(cudaMalloc(&sc.d_cluster_count, sizeof(uint32_t))); + + CUDA_CHECK(cudaMallocHost(reinterpret_cast(&sc.h_frame), + m_image_bytes)); + CUDA_CHECK( + cudaMallocHost(reinterpret_cast(&sc.h_cluster_count), + sizeof(uint32_t))); + if (m_cluster_bytes > 0) { + CUDA_CHECK( + cudaMallocHost(reinterpret_cast(&sc.h_clusters), + m_cluster_bytes)); + } + } + } + + ~ClusterFinderCUDA() { + for (auto &sc : v_sc) { + if (sc.stream) + cudaStreamSynchronize(sc.stream); + + if (sc.d_frame) + cudaFree(sc.d_frame); + if (sc.d_pd_mean) + cudaFree(sc.d_pd_mean); + if (sc.d_pd_sum) + cudaFree(sc.d_pd_sum); + if (sc.d_pd_sum2) + cudaFree(sc.d_pd_sum2); + if (sc.d_clusters) + cudaFree(sc.d_clusters); + if (sc.d_cluster_count) + cudaFree(sc.d_cluster_count); + + if (sc.h_frame) + cudaFreeHost(sc.h_frame); + if (sc.h_clusters) + cudaFreeHost(sc.h_clusters); + if (sc.h_cluster_count) + cudaFreeHost(sc.h_cluster_count); + + if (sc.kernel_start) + cudaEventDestroy(sc.kernel_start); + if (sc.kernel_stop) + cudaEventDestroy(sc.kernel_stop); + if (sc.stream) + cudaStreamDestroy(sc.stream); + } + } + + // Non-copyable, non-movable + ClusterFinderCUDA(const ClusterFinderCUDA &) = delete; + ClusterFinderCUDA &operator=(const ClusterFinderCUDA &) = delete; + ClusterFinderCUDA(ClusterFinderCUDA &&) = delete; + ClusterFinderCUDA &operator=(ClusterFinderCUDA &&) = delete; + + void set_nSigma(COMPUTE_TYPE nSigma) { m_nSigma = nSigma; } + COMPUTE_TYPE get_nSigma() const { return m_nSigma; } + + void push_pedestal_frame(NDView frame) { + m_pedestal.push(frame); + m_pedestal_dirty = true; + } + + void clear_pedestal() { + m_pedestal.clear(); + m_pedestal_dirty = true; + } + + NDArray pedestal() { return m_pedestal.mean(); } + NDArray noise() { return m_pedestal.std(); } + + /** + * @brief Move clusters out of the internal ClusterVector, optionally + * reallocating the internal one with the same capacity. + */ + ClusterVector + steal_clusters(bool realloc_same_capacity = false) { + ClusterVector tmp = std::move(m_clusters); + if (realloc_same_capacity) + m_clusters = ClusterVector(tmp.capacity()); + else + m_clusters = ClusterVector{}; + return tmp; + } + + /** + * @brief Find clusters in a single frame, appending them to the internal + * ClusterVector. + */ + void find_clusters(NDView frame, uint64_t frame_number = 0) { + if (m_pedestal_dirty) { // need to update the pedestal on the gpu + sync_pedestal_to_device(); + m_pedestal_dirty = false; + } + + auto &sc = v_sc[0]; + const uint32_t n_pd_samples = + static_cast(m_pedestal.n_samples()); + + // First, CPU copies frame into a reusable pinned buffer + std::memcpy(sc.h_frame, frame.data(), m_image_bytes); + + // Reset cluster counter + CUDA_CHECK(cudaMemsetAsync(sc.d_cluster_count, 0, sizeof(uint32_t), + sc.stream)); + + // Upload frame + CUDA_CHECK(cudaMemcpyAsync(sc.d_frame, sc.h_frame, m_image_bytes, + cudaMemcpyHostToDevice, sc.stream)); + + // Timed Kernel launch + CUDA_CHECK(cudaEventRecord(sc.kernel_start, sc.stream)); + device::find_clusters_in_single_frame + <<>>( + sc.d_frame, sc.d_pd_mean, sc.d_pd_sum, sc.d_pd_sum2, + n_pd_samples, m_nSigma, nrows, ncols, sc.d_clusters, + sc.d_cluster_count, static_cast(m_capacity)); + CUDA_CHECK(cudaEventRecord(sc.kernel_stop, sc.stream)); + CUDA_CHECK(cudaGetLastError()); + + // Read back cluster count into pinned buffer + CUDA_CHECK(cudaMemcpyAsync(sc.h_cluster_count, sc.d_cluster_count, + sizeof(uint32_t), cudaMemcpyDeviceToHost, + sc.stream)); + + // Synchronize to ensure count is available before the CPU reads + // clusters + CUDA_CHECK(cudaStreamSynchronize(sc.stream)); + + record_kernel_time(sc); + + // Clamp to max in case of overflow + uint32_t n_found = *sc.h_cluster_count; + n_found = std::min(n_found, static_cast(m_capacity)); + + // Read back clusters + m_clusters.set_frame_number(frame_number); + if (n_found > 0) { + append_device_clusters_to(m_clusters, sc, n_found); + } + } + + /** + * @brief Batched cluster finding across multiple frames, using n_streams + * CUDA streams to overlap H2D transfer, kernel, and D2H transfer. + * + * Returns one ClusterVector per input frame (with frame_number set to + * first_frame + i). + */ + std::vector> + find_clusters_batched(NDView frames, + uint64_t first_frame = 0) { + if (m_pedestal_dirty) { + sync_pedestal_to_device(); + m_pedestal_dirty = false; + } + + const size_t n_frames = frames.shape(0); + const uint32_t n_pd_samples = + static_cast(m_pedestal.n_samples()); + + std::vector> results; + results.reserve(n_frames); + for (size_t i = 0; i < n_frames; ++i) { + results.emplace_back(); + results.back().set_frame_number(first_frame + i); + } + + const size_t n_rounds = (n_frames + n_streams - 1) / n_streams; + for (size_t round = 0; round < n_rounds; ++round) { + + // Launch phase: fan out kernels on all streams for this round + for (int k = 0; k < n_streams; ++k) { + // OOB guard + const size_t frame_idx = round * n_streams + k; + if (frame_idx >= n_frames) + continue; + + auto &sc_k = v_sc[k]; + const FRAME_TYPE *h_src = + frames.data() + frame_idx * m_image_size; + + std::memcpy(sc_k.h_frame, h_src, m_image_bytes); + + CUDA_CHECK(cudaMemsetAsync(sc_k.d_cluster_count, 0, + sizeof(uint32_t), sc_k.stream)); + CUDA_CHECK( + cudaMemcpyAsync(sc_k.d_frame, sc_k.h_frame, m_image_bytes, + cudaMemcpyHostToDevice, sc_k.stream)); + + CUDA_CHECK(cudaEventRecord(sc_k.kernel_start, sc_k.stream)); + device::find_clusters_in_single_frame + <<>>( + sc_k.d_frame, sc_k.d_pd_mean, sc_k.d_pd_sum, + sc_k.d_pd_sum2, n_pd_samples, m_nSigma, nrows, ncols, + sc_k.d_clusters, sc_k.d_cluster_count, + static_cast(m_capacity)); + CUDA_CHECK(cudaEventRecord(sc_k.kernel_stop, sc_k.stream)); + CUDA_CHECK(cudaGetLastError()); + + // Queue count D2H immediately after the kernel + CUDA_CHECK(cudaMemcpyAsync( + sc_k.h_cluster_count, sc_k.d_cluster_count, + sizeof(uint32_t), cudaMemcpyDeviceToHost, sc_k.stream)); + } + + // Drain phase: fan in results from all streams + for (int k = 0; k < n_streams; ++k) { + const size_t frame_idx = round * n_streams + k; + if (frame_idx >= n_frames) + continue; + + auto &sc_k = v_sc[k]; + + // Wait for memset -> H2D -> kernel -> count D2H + CUDA_CHECK(cudaStreamSynchronize(sc_k.stream)); + + record_kernel_time(sc_k); + + uint32_t n_found = *sc_k.h_cluster_count; + n_found = std::min(n_found, + static_cast(m_capacity)); + + if (n_found > 0) { + append_device_clusters_to(results[frame_idx], sc_k, + n_found); + } + } + } + + return results; + } + + float avg_kernel_time_ms() const { + return m_frames_processed > 0 ? m_total_kernel_ms / m_frames_processed + : 0.0f; + } + + void reset_timers() { + m_total_kernel_ms = 0.0f; + m_frames_processed = 0; + } + + private: + /** + * Upload the current host pedestal (mean, sum, sum2) to every stream's + * device buffers. Called lazily before a find_clusters call when the + * host pedestal has been updated. + */ + void sync_pedestal_to_device() { + // These return-by-value NDArrays must stay alive until the async + // copies complete, so we synchronise at the end before they go out + // of scope. + NDArray h_mean = m_pedestal.mean(); + NDArray h_sum = m_pedestal.get_sum(); + NDArray h_sum2 = m_pedestal.get_sum2(); + + const size_t bytes = m_image_size * sizeof(PEDESTAL_TYPE); + for (auto &sc : v_sc) { + CUDA_CHECK(cudaMemcpyAsync(sc.d_pd_mean, h_mean.data(), bytes, + cudaMemcpyHostToDevice, sc.stream)); + CUDA_CHECK(cudaMemcpyAsync(sc.d_pd_sum, h_sum.data(), bytes, + cudaMemcpyHostToDevice, sc.stream)); + CUDA_CHECK(cudaMemcpyAsync(sc.d_pd_sum2, h_sum2.data(), bytes, + cudaMemcpyHostToDevice, sc.stream)); + } + for (auto &sc : v_sc) + CUDA_CHECK(cudaStreamSynchronize(sc.stream)); + } + + /** + * Copy n_found clusters from sc.d_clusters into the given ClusterVector + * and block on the transfer. + */ + void append_device_clusters_to(ClusterVector &cv, SC &sc, + uint32_t n_found) { + + CUDA_CHECK(cudaMemcpyAsync(sc.h_clusters, sc.d_clusters, + n_found * sizeof(ClusterType), + cudaMemcpyDeviceToHost, sc.stream)); + + CUDA_CHECK(cudaStreamSynchronize(sc.stream)); + + for (uint32_t i = 0; i < n_found; ++i) + cv.push_back(sc.h_clusters[i]); + } + + void record_kernel_time(SC &sc) { + float ms = 0.0f; + CUDA_CHECK(cudaEventElapsedTime(&ms, sc.kernel_start, sc.kernel_stop)); + m_total_kernel_ms += ms; + m_frames_processed++; + } +}; + +} // namespace aare diff --git a/python/aare/ClusterFinder.py b/python/aare/ClusterFinder.py index 87aee70..93d8596 100644 --- a/python/aare/ClusterFinder.py +++ b/python/aare/ClusterFinder.py @@ -55,25 +55,49 @@ def _cuda_available(): def ClusterFinderCUDA(image_size, cluster_size=(3,3), n_sigma=5, dtype=np.int32, - capacity=1024, n_streams=1): + max_clusters_per_frame=2048, n_streams=4): """ Factory function to create a ClusterFinderCUDA object. Provides a cleaner syntax for the templated ClusterFinderCUDA in C++. API mirrors - ClusterFinder() plus CUDA-specific knobs (n_streams). + ClusterFinder() plus CUDA-specific knobs. + Parameters + ---------- + image_size : tuple of (int, int) + Detector shape as (nrows, ncols). + cluster_size : tuple of (int, int), optional + Cluster window size; default (3, 3). + n_sigma : float, optional + Threshold in units of per-pixel pedestal standard deviation. + dtype : numpy dtype, optional + Cluster value type (np.int32 or np.float32). + max_clusters_per_frame : int, optional + Tight upper bound on clusters per frame. Determines the fixed-size D2H + transfer per frame. Set this high enough to never truncate real frames + but as tight as possible to minimize PCIe traffic. Default 2048. + n_streams : int, optional + Number of CUDA streams for H2D/kernel/D2H pipelining. Default 4. + + Example + ------- .. code-block:: python from aare import ClusterFinderCUDA - cf = ClusterFinderCUDA(image_size=(512, 1024), + cf = ClusterFinderCUDA(image_size=(400, 400), cluster_size=(3, 3), n_sigma=5, - n_streams=4) + n_streams=5) for frame in pedestal_frames: cf.push_pedestal_frame(frame) + + # Batched (recommended for throughput) + results = cf.find_clusters_batched(frames_3d, first_frame=0) + + # Or single-frame (one launch per frame) for i, frame in enumerate(data_frames): cf.find_clusters(frame, frame_number=i) - clusters = cf.steal_clusters() + clusters = cf.steal_clusters() """ if not _cuda_available(): raise RuntimeError( @@ -84,7 +108,7 @@ def ClusterFinderCUDA(image_size, cluster_size=(3,3), n_sigma=5, dtype=np.int32, cls = _get_class("ClusterFinderCUDA", cluster_size, dtype) return cls(image_size, n_sigma=n_sigma, - capacity=capacity, + max_clusters_per_frame=max_clusters_per_frame, n_streams=n_streams) def ClusterCollector(clusterfindermt, dtype=np.int32): diff --git a/python/src/bind_ClusterFinderCUDA.hpp b/python/src/bind_ClusterFinderCUDA.hpp index 7abe76b..3448e3b 100644 --- a/python/src/bind_ClusterFinderCUDA.hpp +++ b/python/src/bind_ClusterFinderCUDA.hpp @@ -8,8 +8,8 @@ #include #include -// #include -#include +#include +// #include namespace py = pybind11; using pd_type = double; @@ -28,18 +28,20 @@ void define_ClusterFinderCUDA(py::module &m, const std::string &typestr) { using ClusterType = Cluster; using CF = ClusterFinderCUDA; + using ContigArr = + py::array_t; py::class_(m, class_name.c_str()) - .def(py::init, pd_type, size_t, int>(), py::arg("image_size"), - py::arg("n_sigma") = 5.0, py::arg("capacity") = 1'000'000, - py::arg("n_streams") = 1) + .def(py::init, float, size_t, int>(), py::arg("image_size"), + py::arg("n_sigma") = 5.0f, + py::arg("max_clusters_per_frame") = 2048, py::arg("n_streams") = 4) .def_property( "nSigma", &CF::get_nSigma, &CF::set_nSigma, R"(Number of sigma above the pedestal to consider a photon during cluster finding.)") .def("push_pedestal_frame", - [](CF &self, py::array_t frame) { + [](CF &self, ContigArr frame) { auto view = make_view_2d(frame); self.push_pedestal_frame(view); }) @@ -63,39 +65,59 @@ void define_ClusterFinderCUDA(py::module &m, const std::string &typestr) { .def( "steal_clusters", [](CF &self, bool realloc_same_capacity) { - ClusterVector clusters = - self.steal_clusters(realloc_same_capacity); - return clusters; + return std::move(self.steal_clusters(realloc_same_capacity)); }, - py::arg("realloc_same_capacity") = false) + py::arg("realloc_same_capacity") = true) .def( "find_clusters", - [](CF &self, py::array_t frame, uint64_t frame_number) { + [](CF &self, ContigArr frame, uint64_t frame_number) { auto view = make_view_2d(frame); self.find_clusters(view, frame_number); }, - py::arg("frame"), py::arg("frame_number") = 0) + py::arg("frame"), py::arg("frame_number") = 0, + py::call_guard()) .def( "find_clusters_batched", - [](CF &self, py::array_t frames, uint64_t first_frame) { - // frames is expected as a 3D numpy array (n_frames, nrows, - // ncols) + [](CF &self, ContigArr frames, uint64_t first_frame) { auto view = make_view_3d(frames); return self.find_clusters_batched(view, first_frame); }, py::arg("frames"), py::arg("first_frame") = 0, - R"(Process a 3D array of frames (n_frames, nrows, ncols) in parallel - across the configured CUDA streams. Returns a list of ClusterVector, one per - input frame.)") + py::call_guard(), + R"(Process a 3D array of frames (n_frames, nrows, ncols) using + n_streams CUDA streams for H2D/kernel/D2H pipelining. Returns a + list of ClusterVector, one per input frame. The input array is + converted to C-contiguous uint16 if needed.)") .def("avg_kernel_time_ms", &CF::avg_kernel_time_ms, R"(Average kernel execution time per frame in milliseconds, - excluding PCIe transfers. Use wall_time - avg_kernel_time to estimate transfer overhead.)") + excluding PCIe transfers.)") .def("reset_timers", &CF::reset_timers, - R"(Reset the internal kernel timing counters.)"); + R"(Reset the internal kernel timing counters.)") + + .def( + "register_input_buffer", + [](CF &self, py::array arr) { + auto info = arr.request(); + self.register_input_buffer( + info.ptr, static_cast(info.size) * + static_cast(info.itemsize)); + }, + R"(Pin a numpy array as a locked host buffer so that + find_clusters_batched transfers it at full DMA bandwidth + (~22 GB/s) instead of going through the CUDA driver's + internal staging (~15 GB/s for pageable memory). + + Call once before the processing loop with the full data + array. Slices of that array passed to find_clusters_batched + lie within the registered region and benefit automatically. + Call unregister_input_buffer() when done.)") + + .def("unregister_input_buffer", &CF::unregister_input_buffer, + "Release the previously pinned input buffer."); } } // namespace aare diff --git a/python/tests/ClusterFinderCUDA.ipynb b/python/tests/ClusterFinderCUDA.ipynb index 9723964..be4c139 100644 --- a/python/tests/ClusterFinderCUDA.ipynb +++ b/python/tests/ClusterFinderCUDA.ipynb @@ -53,7 +53,7 @@ "text": [ "Image size: (400, 400)\n", "Pedestal frames: 1000\n", - "Data frames: 88999\n" + "Data frames: 20000\n" ] } ], @@ -62,10 +62,10 @@ "f = File(base / 'Moench03new/cu_half_speed_master_4.json')\n", "\n", "n_frames_pd = 1000\n", - "N = 88999\n", + "N = 20000 #88999\n", "cluster_size = (9, 9)\n", "image_size = (f.rows, f.cols)\n", - "capacity = 1_000 #3_000_000\n", + "capacity = 50_000 #3_000_000\n", "\n", "print(f'Image size: {image_size}')\n", "print(f'Pedestal frames: {n_frames_pd}')\n", @@ -136,17 +136,33 @@ { "cell_type": "code", "execution_count": 7, - "id": "cbdcb805-708b-4205-bda9-2aa163d0e81f", + "id": "00e2f63a-5e64-4d3e-becf-5e2835c6d712", "metadata": {}, "outputs": [], "source": [ - "N_STREAMS = 5\n", - "cf_cuda = ClusterFinderCUDA(image_size, cluster_size, n_sigma=7, capacity=capacity, n_streams=N_STREAMS)" + "# Runs the destructor under the hood in case cf_cuda has already been constructed\n", + "# del cf_cuda\n", + "cf_cuda = None" ] }, { "cell_type": "code", "execution_count": 8, + "id": "cbdcb805-708b-4205-bda9-2aa163d0e81f", + "metadata": {}, + "outputs": [], + "source": [ + "N_STREAMS = 5\n", + "cf_cuda = ClusterFinderCUDA(image_size, \n", + " cluster_size, \n", + " n_sigma=7, \n", + " max_clusters_per_frame=2048,\n", + " n_streams=N_STREAMS) " + ] + }, + { + "cell_type": "code", + "execution_count": 9, "id": "1546f405-1bf6-4073-ab35-8134be695a6c", "metadata": {}, "outputs": [ @@ -154,7 +170,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Pedestal (1000 frames): 0.515s\n" + "Pedestal (1000 frames): 0.496s\n" ] } ], @@ -177,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "4573be3d-5ba8-4e18-bab0-c874f2b7dcb2", "metadata": {}, "outputs": [ @@ -185,7 +201,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Reading 88999 frames: 4.574s (19458 FPS, 5938.097 GB/s)\n" + "Reading 20000 frames: 1.195s (16742 FPS, 5109.275 GB/s)\n" ] } ], @@ -208,7 +224,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "fbb14fda-2852-4b73-ba2c-9952abac1d99", "metadata": {}, "outputs": [ @@ -216,7 +232,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU clustering: 837.700s (106 FPS, 90799856 clusters, 1020.23/frame)\n" + "CPU clustering: 188.615s (106 FPS, 20325971 clusters, 1016.30/frame)\n" ] } ], @@ -257,7 +273,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 12, "id": "4f94cf35-5796-463d-bed8-f44a02d91fc6", "metadata": {}, "outputs": [], @@ -267,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 45, "id": "50848f8c-2e66-45b8-b6d5-ca80f368e6ba", "metadata": {}, "outputs": [], @@ -275,6 +291,9 @@ "if(BATCHED):\n", " BATCH_SIZE = 5000\n", "\n", + " # Before warmup, pin the entire data array once\n", + " cf_cuda.register_input_buffer(data)\n", + "\n", " # Warmup: first kernel launch pays CUDA context + pedestal H2D upload cost\n", " _ = cf_cuda.find_clusters_batched(data[0:BATCH_SIZE], first_frame=0)\n", " \n", @@ -288,14 +307,17 @@ " cf_cuda.find_clusters_batched(data[start:stop], first_frame=start)\n", " )\n", " t_cuda = time.perf_counter() - t0\n", + "\n", + " cf_cuda.unregister_input_buffer() # release when done with this dataset\n", + "\n", " kernel_ms = cf_cuda.avg_kernel_time_ms()\n", " \n", " n_clusters_cuda = sum(cv.size for cv in clusters_cuda_per_frame)\n", "\n", " hist_cuda = make_hist_from_batch(clusters_cuda_per_frame)\n", " \n", - "else:\n", - " # Warmup\n", + "else: \n", + " # Simpler: (non-batched) per-frame run on non-pinned data\n", " cf_cuda.find_clusters(data[0])\n", " _ = cf_cuda.steal_clusters(realloc_same_capacity=False)\n", "\n", @@ -322,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 46, "id": "502d0d3b-6b1e-4cc9-91df-9d998bd849b5", "metadata": {}, "outputs": [ @@ -332,7 +354,7 @@ "(9, 9)" ] }, - "execution_count": 21, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -343,7 +365,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 47, "id": "2e3e4b9c-7f23-4fb7-bc3e-fa3d766d51fc", "metadata": {}, "outputs": [ @@ -351,7 +373,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU clustering: 837.700s (106 FPS, 90799856 clusters, 1020.23/frame)\n" + "CPU clustering: 188.615s (106 FPS, 20325971 clusters, 1016.30/frame)\n" ] } ], @@ -362,7 +384,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 48, "id": "4b8df93b-9a1b-41a5-9fed-9cda295fb523", "metadata": {}, "outputs": [ @@ -370,10 +392,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "CUDA clustering: 6.261s (14214 FPS, 88537254 clusters, 994.81/frame)\n", - " Kernel only: 0.040 ms/frame\n", - " PCIe + overhead: 0.030 ms/frame\n", - "Speedup (CPU / CUDA): 133.79×\n" + "CUDA clustering: 1.304s (15332 FPS, 20238568 clusters, 1011.93/frame)\n", + " Kernel only: 0.038 ms/frame\n", + " PCIe + overhead: 0.027 ms/frame\n", + "Speedup (CPU / CUDA): 144.60×\n" ] } ], @@ -397,7 +419,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 49, "id": "d3a850df-7df0-485b-971d-381cfdc6be81", "metadata": {}, "outputs": [ @@ -405,7 +427,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Cluster count diff: 2262602 (2.49%)\n" + "Cluster count diff: 87403 (0.43%)\n" ] } ], @@ -425,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 50, "id": "aff8db7e-8332-4228-857f-a9875cf940d3", "metadata": {}, "outputs": [ @@ -445,14 +467,14 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 51, "id": "9adeea2f-9309-4c0a-905b-7d1203ba1f4e", "metadata": {}, "outputs": [ { "data": { "image/png": - "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6dWQngopW5V/Yyr6mXbt2qWOWbYrU8nK3yGZklhmtpLARU8hk9+VfMdlNjNrM6bp9SBBieW4IqJqz96jx4moepDZcGTWmbp162re3t5aQECAds0112gvv/yyFhcXV+ysUOLChQvafffdp4WFhanZmB599FHt999/N5u95dKlS9qQIUO05s2bq23LDDNt27bVZs+ebZyVRZ9pp1evXlpQUJB6v8wQo0tPT9defPFFNQOTHKPsq02bNtq//vUvNfuMTt43atSocn1+mfWne/fuapYjmQ1H9iufaf369cYyUj9y7GWZiUhm8nnrrbe0du3aab6+vurzymcfMWKEduzYsTLNgHPgwAGtZ8+e6v1St0OHDtU+//xzta8//vijSHmpO3ntqaeeKtNnLuvvRD9Gmd1JZmqSuq9fv742a9asItss6++ooKBA7ad169bGcp07d9Z++OGHUs8FfVao7777zurn2rFjh9alSxf1mWTWH/n9fPLJJ1ZnhbJW99bOH32WszfffLPIc3JelNWmTZvUeSWzTnl5eanzTT63bDc1NbXUY7NG6vXpp59W312Z4Uzq66abbiq2fopz9OhR9Xnktm7dOrPXsrOz1Xkl54ccs8ymJb9jqVvT2bOq4m9MeT+T/v3Tb/L9lfqVc+edd94xq1f53kiZkma3Onz4sCpjOVMckStwk3/sHdwQEZFtyAJlMmOPpFqZDuIVsuCcpNpIGonpoNvKkl4duaKsLxxHRETVE1OhiIiqKUnlkRQkSQOSHHxp2MuMPDIOwjSokHQSmXVHykuaVVUGFURE5DoYWBARVVMyLkPWmJCxGTLNpoyDkNxvyUE3JeM1ZIYlGbtQ2uxaRERExWEqFBERERERVRqnmyUiIiIiokpjYEFERERERJXGwIKIiIiIiCqNg7dtTBZ7koV3ZHGe8q5qS0RERERkS7IyRVpamppl0HJxVUsMLGxMgoo6derYerdERERERBV25swZ1K5du8QyDCxsTHoq9F9OcHCwzSPOlJQUhISEsLeE9cbzzUHxe8p64znnHPhdZb25yvmWmpqqLorrbdiSMLCwMf1kkKDCHoGF3GS/TMNivfF8c0z8nrLeeM45B35XWW+udr65lWG/HLxNRERERESVxsCCiIiIiIgqjYEFERERERFVGsdYEBER0VVXUFCAvLy8alPTku+em5uL7Oxsjltk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EBERERE5sHz/Wvg8/zZ178gYWLgQ34T9WOk9Rd0TERERkXPI94/Ed4Xd1L0jY2BBREREROTA3HNS0MntkLp3ZAwsiIiIiIgcmHfaabzk9aW6d2QMLIiIiIiIqNIYWBARERERUaUxsHAhOTWaYFjev9Q9EREREVFVYmDhQjRPX1zQItQ9ERERETkHzcMHp7Sa6t6RMbBwIV6ppzHR81t1T0RERETOISe0KUbljVf3joyBRQXMnTsXDRo0gK+vLzp06IBff/0VzsAjNwXd3f9Q90REREREVYmBRTktWbIE48ePx5QpU7Bnzx7cfPPN6NOnD06fZi8AEREREVU938sHscRrhrp3ZAwsymnWrFkYOnQonnzySbRo0QJz5sxBnTp1MG/evKvzGyIiIiIi16YVIsAtR907Mk97H4Azyc3Nxa5du/D888+bPd+rVy9s3brV6ntycnLUTZeSkmK81zQNtpSWlg7v7EJ1rx8HlU5+T3p9ubm5scrKiPVWMaw31put8ZxjvfF8c3xpdmzDpaamqvuytFsZWJRDQkICCgoKUKtWLbPn5fHFixetvmfmzJmYPn16kefr1q0Lu5l2h/32TURERERO14ZLS0tDSEhIiWUYWFSA5VVrieCKu5I9efJkTJgwwfi4sLAQiYmJCA8Pt/nVb4k4JW3rzJkzCA4Otum+nRnrjfXG883x8XvKuuM55xz4XXW+epN2rgQVMTExpZZlYFEOERER8PDwKNI7ERcXV6QXQ+fj46NupmrUqAF7khOSgQXrjeebY+P3lPXGc8458LvKenOF8y2klJ4KHQdvl4O3t7eaXnbdunVmz8vjLl26lO83RERERERUjbDHopwkrWnQoEHo2LEjOnfujI8++khNNfvUU09dnd8QEREREZETYGBRTg888AAuX76MGTNm4MKFC2jdujVWr16NevXqwdFJStbUqVOLpGYR643nm+Pg95T1xnPOOfC7ynrj+VaUm2brOU+JiIiIiKja4RgLIiIiIiKqNAYWRERERERUaQwsiIiIiIio0hhYEBERERFRpTGwICIiIiKiSmNgQURERERElcbAgoiIiIiIKo2BBRERERERVRoDCyIiIiIiqjQGFkREREREVGkMLIiIiIiIqNIYWBARERERUaUxsCAiIiIiokpjYEFERERERJXGwIKIiIiIiCqNgQUREREREVUaAwsiIiIiIqo0BhZERERERFRpLhtYzJw5E9dddx2CgoIQGRmJAQMG4MiRI6W+b9OmTejQoQN8fX3RsGFDzJ8/3ybHS0RERETkyFw2sJAAYdSoUdi+fTvWrVuH/Px89OrVCxkZGcW+58SJE7jjjjtw8803Y8+ePXjhhRcwduxYLF261KbHTkRERETkaNw0TdPsfRCOID4+XvVcSMBxyy23WC3z3HPP4fvvv8ehQ4eMzz311FP4448/sG3bNhseLRERERGRY/G09wE4ipSUFHUfFhZWbBkJHqRXw1Tv3r3x6aefIi8vD15eXkXek5OTo266wsJCJCYmIjw8HG5ublX6GYiIiIiIqpL0QaSlpSEmJgbu7iUnOzGw+KfCJkyYgJtuugmtW7cutrIuXryIWrVqmT0njyWNKiEhAdHR0VbHckyfPr3iv00iIiIiIjs7c+YMateuXWIZBhYARo8ejX379mHLli2lVqplL4OeSVZc78PkyZNV0GLaM1K3bl2cOnUKwcHBsCXpLZEAKCIiotSIk1hvPN/sg99T1hvPOefA7yrrzVXOt9TUVNSrV09NeFQalw8sxowZo8ZNbN68udQoLCoqSvVamIqLi4Onp6dKbbLGx8dH3SzVqFHDLoFFbm6u2jcDC9YbzzfHxO8p643nnHPgd5X15irnm/s/+ytLCr/LXraWngbpqVi2bBk2bNiABg0alPqezp07qxmkTK1duxYdO3a0Or6CiIiIiMhVuGxgIVPNfvnll/j6669V1470RMgtKyvLLI3pscceM5sBSlKYJLVJZob67LPP1MDtSZMm2elTEBERERE5BpcNLObNm6fGO3Tr1k0NutZvS5YsMZa5cOECTp8+bXwsvRqrV6/GL7/8gvbt2+Pf//433n33Xdx77712+hRERERERI7BZcdYlGX5joULFxZ5rmvXrti9e/dVOioiIiIiIufksj0WRERERERUdRhYEBERERFRpTGwICIiIiKiSmNgQURERERElcbAgoiIiIiIKo2BBRERERERVRoDCyIiIiIiqjQGFkREREREVGkMLIiIiIiIqNIYWBARERERUaUxsCAiIiIiokpjYEFERERERJXGwIKIiIiIiCqNgQUREREREVWaJ5zEE088YfX5kJAQNGvWDI8++igCAwNtflxEREREROREPRZJSUlWb3v37sXLL7+sgou///7b3odJREREROSSnKbHYvny5cW+lpWVhcceewzPP/88vv32W5seFxEREREROVGPRUn8/Pzw3HPPYfv27fY+FCIiIiIil1QtAgsRFhaG5ORkex8GEREREZFLqjaBxdatW9GoUSN7HwYRERERkUtymjEW+/bts/p8SkoKdu7ciddeew2vvPKKzY+LiIiIiIicKLBo37493NzcoGlakddq1qypxlg89dRTdjk2IiIiIiJX5zSBxYkTJ4pdx6JGjRo2Px4iIiIiInLCwKJevXrqPicnB/n5+QgICLD3IRERERERkbMN3k5ISMCdd96pVtcODg5Gly5duCAeEREREZGDcJrAYvLkydi1axemT5+ON998UwUaI0aMsPdhERERERGRM6VC/fTTT/jss89wxx13qMdy37p1a+Tl5cHLy8veh0dERERE5NKcpsfi/PnzuOaaa4yPmzdvDm9vb/U8ERERERHZl9MEFjLNrKeneQeLPC4sLLTbMRERERERkRMGFj169MC1115rvGVmZqJfv35mz5XV5s2b1XtjYmLU+hgrVqwosfwvv/yiylneDh8+XAWfjoiIiIjIuTnNGIupU6cWee6uu+6q8PYyMjLQrl07PP7447j33nvL/L4jR46oWalMF+cjIiIiInJ1Th1YVEafPn3UrbwiIyO5IB8RERERkbMGFtnZ2Vi7di26d++OoKAgs9dSU1NVqlLv3r3h4+NzVY9DBpDLsbRs2RIvvviiOp6SyIJ+cjM9ViFjQ2w9PkT2JyllHJfCeuP55rj4PWW98ZxzDvyust5c5XwrLMc+nSaw+PDDD/H999+jf//+RV6T1KR3330Xp0+fxujRo6/K/qOjo/HRRx+hQ4cOKlBYtGiRGvMhAc0tt9xS7Ptmzpyp1t6wFB8frwIUW58YKSkp6sR0d3ea4TV2x3pjvfF8c3z8nrLueM45B35Xna/e0tLSylzWTZMjdALXX389XnrpJTXg2pqVK1dixowZ2LFjR7m3LYOwly9fjgEDBpTrfXIs8l4JeMrTY1GnTh0kJSWZjdWw1UkpAY2MC2FgwXrj+eaY+D1lvfGccw78rrLeXOV8S01NRWhoqApsSmu7Ok2PxbFjx9Rg6+K0bdtWlbGlG264AV9++WWJZSQ1y1p6lpwU9mjcSyBkr307M9Yb643nm+Pj95R1x3POOfC76lz1Vp79OU3rMj8/X0VqxZHXpIwt7dmzR6VIERERERG5OqfpsWjVqhXWr1+vxjhYs27dOlWmrNLT03H8+HHj4xMnTmDv3r0ICwtD3bp1MXnyZJw7dw5ffPGFen3OnDmoX7++2kdubq7qqVi6dKm6ERERERG5OqcJLJ544glMmDBBNez79u1r9toPP/yAV155BbNmzSrz9n7//XezGZ1k22Lw4MFYuHAhLly4oAaD6ySYmDRpkgo2/Pz81HGsWrUKd9xxR5V8PiIiIiIiZ+Y0gcXw4cPVatkyK1Tz5s3RrFkzlWt26NAhHD16FAMHDlRlyqpbt25qZH1xJLgw9eyzz6obERERERE58RgLIelHixcvRtOmTVUwcfjwYRVgfPPNN+pGRERERET24TQ9FjrpmZAbERERERE5DqfqsSAiIiIiIsfEwIKIiIiIiCqNgQUREREREVUaAwsiIiIiInKdwCImJgZPP/00fvzxR7WmBBEREREROQ6nCSy+/vpr+Pv7Y+zYsYiIiMD999+PRYsWITEx0d6HRkRERETk8pwmsJAF7d5++20cO3YM27Ztw7XXXosPPvgA0dHR6rXZs2fjr7/+svdhEhERERG5JKcJLEy1atUKkydPxvbt23H69Gk88sgj2LBhA9q0aYPWrVtj1apV9j5EIiIiIiKX4nQL5FmqVasWhg0bpm6ZmZn46aef4OPjY+/DIiIiIiJyKU4fWJiSMRh33323vQ+DiIiIiMjlOGUqFBERERERORYGFkREREREVGkMLIiIiIiIqNKqRWBRWFiIH374AQMGDLD3oRARERERuSSnDixkTQuZdrZ27doYOHCgvQ+HiIiIiMhlOd2sUFlZWfj222/x6aefqnUsCgoK1OJ4TzzxBAIDA+19eERERERELslpeix27NiB4cOHIyoqCu+//z7uvfdenDlzBu7u7ujZsyeDCiIiIiIiO3KaHosuXbpgzJgxKsBo1qyZvQ+HiIiIiIicMbC49dZbVfpTXFwcBg0ahN69e8PNzc3eh0VERERERM6UCrV27VocPHhQ9VY8/fTTiI6Oxrhx49RrDDCIiIiIiOzLaQILUadOHbz88ss4ceIEFi1apHovPD09cdddd+GFF17A7t277X2IREREREQuyakCC1O33XYbvvnmG5w/f16Nvfjxxx9x3XXX2fuwiIiIiIhcktMGFrrQ0FAVWOzZswc7d+609+EQEREREbkkpxm8ra+wvXDhQixbtgwnT55UYysaNGiA++67Tw3ovvbaa+19iERERERELslpeiw0TUP//v3x5JNP4ty5c2jTpg1atWqFU6dOYciQIbj77rvtfYhERERERC7LaXospKdi8+bN+Pnnn9G9e3ez1zZs2IABAwbgiy++wGOPPWa3YyQiIiIiclVO02MhA7Vl5ifLoEJf4+L555/HV199VebtSZDSr18/xMTEqJSqFStWlPqeTZs2oUOHDvD19UXDhg0xf/78cn8OIiIiIqLqyGkCi3379uH2228v9vU+ffrgjz/+KPP2MjIy0K5dO7z//vtlKi9T3N5xxx24+eab1UBxCXLGjh2LpUuXlnmfRERERETVldOkQiUmJqJWrVrFvi6vJSUllXl7EojIraykd6Ju3bqYM2eOetyiRQv8/vvveOutt3DvvfeWeTtERERERNWR0wQWBQUFajG84nh4eCA/P/+q7X/btm3o1auX2XO9e/fGp59+iry8PHh5eV21fRMRERE5k9OnTyMhIcH4OCIiQl2gLalcZcrY+3OQkwUWMiuUzP7k4+Nj9fWcnJyruv+LFy8W6TGRxxLMyAkXHR1d7HGZHltqaqpx6ly52ZLsT+rR1vt1dqw31hvPN8fH7ynrzt7n3JkzZ8wav3Xq1Cnx/eUtX5H3m5axpfj4eNx///3IzMw0Pufv74/vvvtOHWt2djbOnj2rjs20nF6mZs2axW7LWhl7fA5b7N+UnGve3t6q/mytPO1GpwksBg8eXGqZqz0jlAzytvwlW3ve1MyZMzF9+nSrg9H9/PxgS3K8EghJz09Jx0ysN55v9sPvaenkYo3eQy1/z+SCk9RbVlaW8e8ylb/h4O7uNMMuHa7e5Hw8duyYsQEmzzVp0qTYTIvylq/I+y3L2KPdph+Tfizff/+9ei0gIECNdTUtJ0zLWNtWSWXs8TlsLTAwEI0aNSr2IvvVIn9bq11gsWDBArvuPyoqSvVamIqLi1MnWnh4eLHvmzx5MiZMmGDWYyFXFR566CEEBwfDluSPi0TfEmXzPxDWG883x2Sr76m9rmRWVnFXLyWgkAs5krbKXtnykfNMFpjdvXs3664S9aafi8LyKrc15S1fkffb6+q6tV4U/W+OfFelx0Jm2JSLnKblivu7VJYytv4ctnb48GG8++67GDhwoM0XhJa268iRI6tXYGFvnTt3xg8//GD23Nq1a9GxY8cSx1dIVGktspQ/SPZo3MuX2F77dmasN+erN8u82LIwzZ2tyPurgv6f7vnz569az6I0zu+5555yN2QchTSWVq9erX6WzyETccg51qVLF/V3OjIy0t6H6FQsG3pUsXoz/ftx8ODBUv9+lLd8Rd7vSOMB6tWrp24ShMmFWfmeWv7foJcpy3bsxZ771zTN+P+qLZVnf04TWMj6Fdb+4IWEhKBZs2YYNWpUufIT09PTcfz4cbPpZPfu3YuwsDD1JZSeBlnhWxbdE0899ZSamlZ6H4YNG6auisnAbUlpInJ11hrh+n+68j2ydWOlog1nabAuW7ZM/Wyvhrf8AZf1cnbt2nVVrx7LZ12zZo1drmRWlmlj6dChQ8aroJJ/3KpVK144KaeSGnpUsXqT87M8Dfrylq/q9xNVFacJLNq3b2/1+eTkZHXlShr9W7ZsKbacJZkq1nSxPT1dSXLpZJXvCxcuqMaSrkGDBmo///rXv/DBBx+ohfWkS4pTzZK9lHRF3ZZX3otrxNuqgVxVDWf9c+jr5dir4W2rq8eOdCWzMvQGld7IIyIi+3GawGL27Nklvi49FrJond49Xppu3bqVOMhPggtLXbt2VbmUVL1U1TR2tkydKe2KvK2vvFtrhNs7vaIiv0v96ndF318VePWYiIicldMEFqUZMWKEWleCqq/KBADFNfotG+h6g9xaA7mklB575KwXd0XdHlferf0+nLGBzHQCIiKiiqs2gYVM3SqNP3I+ZbnSX5YAoKzvtaQ3vIVpg7y8KT22Tp0pKbhyhCvvRERE5FqqTWAhMzQ1bdrU3odRLRr0Zc3Pr4o8/vJc6S8tACjLe601+q0NBDVV1pQeR2rA88o7ERER2ZrTBBbFLUaSkpKCnTt3qhmarI2LoKINeUnpkcZzcQ36subnV1Uef1mv9JcWAJT1veVtkDtjSg8RERGRrTlNYDFgwACrzwcFBaF58+YqqJAFYqh4sqiLLHCydetW42I+lg36subnV2Uef0Wu9POKPBEREZFjcZrAgiupVp5c4c/JyVFrc7Ro0aLYBn1Z8/OZx09EREREThdYUNWRHp6SloMva28Aew2IiIiISOc0CeMbNmxAy5YtkZqaanWchay2+uuvv9rl2IiIiIiIXJ3TBBZz5szBsGHDEBwcXOS1kJAQtY7FrFmz7HJsRERERESuzmkCiz/++KPE6UV79eql1hkgIiIiIiLbc5rA4tKlS/Dy8ir2dU9PTzVTERERERER2Z7TBBaxsbHYv39/sa/v27cP0dHRNj0mIiIiIiJyssDijjvuwMsvv6xWQLaUlZWFqVOnom/fvnY5NiIiIiIiV+c0082++OKLapXnpk2bYvTo0WjWrBnc3NzUWgoffPABCgoKMGXKFHsfJhERERGRS3KawKJWrVpqxeinn34akydPhqZp6nkJLnr37o25c+eqMkREREREZHtOE1iIevXqYfXq1UhKSsLx48dVcNGkSROEhoba+9CIiIiIiFyaUwUWOgkkrrvuOnsfBhEREREROdvgbSIiIiIiclwMLIiIiIiIqNIYWBARERERUaUxsCAiIiIiokpjYEFERERERJXGwIKIiIiIiCqNgQUREREREVUaAwsiIiIiIqo0BhZERERERFRpDCyIiIiIiKjSGFgQEREREVGlMbAgIiIiIqJKc/nAYu7cuWjQoAF8fX3RoUMH/Prrr8VW1i+//AI3N7cit8OHD1f+N0FERERE5MRcOrBYsmQJxo8fjylTpmDPnj24+eab0adPH5w+fbrE9x05cgQXLlww3po0aWKzYyYiIiIickQuHVjMmjULQ4cOxZNPPokWLVpgzpw5qFOnDubNm1fi+yIjIxEVFWW8eXh42OyYiYiIiIgckcsGFrm5udi1axd69epl9rw83rp1a4nvveaaaxAdHY0ePXpg48aNV/lIiYiIiIgcnydcVEJCAgoKClCrVi2z5+XxxYsXrb5HgomPPvpIjcXIycnBokWLVHAhYy9uueUWq++RcnLTpaSkqPvk5GQUFhbCltLS0tQ+5V72T2UjdZaamgpvb2+4u7tsLF5urDfWG88358DvKuuN55vjS7NjG07aQELTtFLLumxgoZPB16ak0iyf0zVr1kzddJ07d8aZM2fw1ltvFRtYzJw5E9OnTy/yfL169WAv3bt3t9u+iYiIiMj52nAS1ISEhJRYxmUDi4iICDU2wrJ3Ii4urkgvRkluuOEGfPnll8W+PnnyZEyYMMH4WKLNxMREhIeHFxvAXM2IU8aQSDAUHBxs0307M9Yb643nm+Pj95R1x3POOfC76nz1JhfdJaiIiYkptazLBhaS1iIpTevWrcPdd99tfF4e33XXXWXejswmJSlSxfHx8VE3UzVq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w26rkrkXEREREfEnBRZhJPrwNjwQ9am5FxERERHxJwUWhfDGG2+gXr16iIuLQ+vWrfHzzz8jGJTKSMVFkb+ZexEREREJDlZEFFKteHMfyBRYeOmTTz7BsGHDMGrUKKxcuRIXXHABLrvsMmzbpl4AEREREfG/9IpNcXPmKHMfyBRYeGncuHEYMGAA7rjjDjRt2hQTJkxA7dq18eabbxbNJyQiIiIiEgQCuz8lwGRkZGD58uV45JFHsi3v3r07Fi1a5PE56enp5mZLTU113luWheJ05MhRxJzIMvf2fkj++DnZ7RUREaEmKyC1W+Go3dRuxU3HnNpNx1vgy9i6HC+fehEZW0sjNaFNsb724cOHzX1BzlsVWHhh//79OHXqFKpWrZptOX/fs2ePx+eMGTMGo0ePzrG8Tp06KDFPXV5yry0iIiIihTO6G0rKkSNHkJiYmOc6CiwKwf2qNSO43K5kjxw5EsOHD3f+npWVhYMHD6JixYrFfvWbESfTtrZv345y5coV62sHM7Wb2k3HW+DT91Rtp2MuOOi7GnztxvNcBhU1atTId10FFl6oVKkSSpUqlaN3Yt++fTl6MWyxsbHm5qp8+fIoSTwgFVio3XS8BTZ9T9VuOuaCg76rardwON4S8+mpsGnwthdiYmJMedn58+dnW87fO3bs6N0nJCIiIiISQtRj4SWmNfXr1w9t2rRBhw4dMHnyZFNq9q677iqaT0hEREREJAgosPDS9ddfjwMHDuDpp5/G7t270aJFC8yZMwd169ZFoGNK1pNPPpkjNUvUbjreAoe+p2o3HXPBQd9VtZuOt5wirOKueSoiIiIiIiFHYyxERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnCixERERERMRnYRtYjBkzBm3btkVCQgKqVKmCq6++Ghs2bMj3eQsWLEDr1q0RFxeHM888E5MmTSqW/RURERERCWRhG1gwQBgyZAiWLFmC+fPn4+TJk+jevTuOHTuW63O2bNmCyy+/HBdccAFWrlyJRx99FPfeey9mzJhRrPsuIiIiIhJoIizLskp6JwJBcnKy6blgwHHhhRd6XOfhhx/GrFmzsG7dOueyu+66C7/99hsWL15cjHsrIiIiIhJYokp6BwJFamqquU9KSsp1HQYP7NVw1aNHD7z77rvIzMxEdHR0juekp6ebmy0rKwsHDx5ExYoVERER4df3ICIiIiLiT+yDOHLkCGrUqIHIyLyTnRRY/NNgw4cPR6dOndCiRYtcG2vPnj2oWrVqtmX8nWlU+/fvR/Xq1T2O5Rg9enThP00RERERkRK2fft21KpVK891FFgAuOeee7B69WosXLgw30Z172WwM8ly630YOXKkCVpce0bq1KmDrVu3oly5cihO7C1hAFSpUqV8I05Ru+l4Kxn6nqrddMwFB31X1W7hcrwdPnwYdevWNQWP8hP2gcXQoUPNuImffvop3yisWrVqptfC1b59+xAVFWVSmzyJjY01N3fly5cvkcAiIyPDvLYCC7WbjrfApO+p2k3HXHDQd1XtFi7HW+Q/r1eQFP6wvWzNngb2VHz++ef4/vvvUa9evXyf06FDB1NBytW8efPQpk0bj+MrRERERETCRdgGFiw1O23aNHz00Uema4c9EbylpaVlS2O65ZZbslWAYgoTU5tYGeq9994zA7dHjBhRQu9CRERERCQwhG1g8eabb5rxDl26dDGDru3bJ5984lxn9+7d2LZtm/N39mrMmTMHP/74I1q1aoV///vfePXVV3HttdeW0LsQEREREQkMYTvGoiDTd0yZMiXHss6dO2PFihVFtFciIiIiIsEpbHssRERERETEfxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIzxRYiIiIiIiIz6IQJG6//XaPyxMTE9G4cWP07dsXZcuWLfb9EhERERGRIOqxSElJ8XhbtWoVnnjiCRNc/PXXXyW9myIiIiIiYSloeixmzpyZ62NpaWm45ZZb8Mgjj+DTTz8t1v0SEREREZEg6rHIS+nSpfHwww9jyZIlJb0rIiIiIiJhKSQCC0pKSsKhQ4dKejdERERERMJSyAQWixYtQv369Ut6N0REREREwlLQjLFYvXq1x+WpqalYtmwZnnvuOTzzzDPFvl8iIiIiIhJEgUWrVq0QEREBy7JyPFa5cmUzxuKuu+4qkX0TEREREQl3QRNYbNmyJdd5LMqXL1/s+yMiIiIiIkEYWNStW9fcp6en4+TJkyhTpkxJ75KIiIiIiATb4O39+/ejZ8+eZnbtcuXKoWPHjpoQT0REREQkQARNYDFy5EgsX74co0ePxksvvWQCjTvvvLOkd0tERERERIIpFeqbb77Be++9h8svv9z8zvsWLVogMzMT0dHRJb17IiIiIiJhLWh6LHbt2oVzzjnH+XuTJk0QExNjlouIiIiISMkKmsCCZWajorJ3sPD3rKysEtsnEREREREJwsCia9euOPfcc52348eP44orrsi2rKB++ukn89waNWqY+TG++OKLPNf/8ccfzXrut/Xr1/vh3YmIiIiIBLegGWPx5JNP5lh21VVXFXp7x44dw9lnn43bbrsN1157bYGft2HDBlOVynVyPhERERGRcBfUgYUvLrvsMnPzVpUqVTQhn4iIiIhIsAYWJ06cwLx583DRRRchISEh22OHDx82qUo9evRAbGxske4HB5BzX5o1a4bHHnvM7E9eOKEfb677ShwbUtzjQ/h6TCnTuBS1m463wKXvqdpNx1xw0HdV7RYux1uWF68ZNIHFW2+9hVmzZuHKK6/M8RhTk1599VVs27YN99xzT5G8fvXq1TF58mS0bt3aBApTp041Yz4Y0Fx44YW5Pm/MmDFm7g13ycnJJkAp7gMjNTXVHJiRkUEzvKbEqd3UbjreAp++p2o7HXPBQd/V4Gu3I0eOFHjdCIt7GATatWuHxx9/3Ay49uSrr77C008/jaVLl3q9bQ7CnjlzJq6++mqvnsd94XMZ8HjTY1G7dm2kpKRkG6tRXAclAxqOC1FgoXbT8RaY9D1Vu+mYCw76rqrdwuV4O3z4MCpUqGACm/zOXYOmx2LTpk1msHVuzjrrLLNOcWrfvj2mTZuW5zpMzfKUnsWDoiRO7hkIldRrBzO1m9pNx1vg0/dUbadjLjjouxpc7ebN6wXN2eXJkydNpJYbPsZ1itPKlStNipSIiIiISLgLmh6L5s2b49tvvzVjHDyZP3++Waegjh49ij///NP5+5YtW7Bq1SokJSWhTp06GDlyJHbu3In//Oc/5vEJEybgjDPOMK+RkZFheipmzJhhbiIiIiIi4S5oAovbb78dw4cPNyf2vXr1yvbYl19+iWeeeQbjxo0r8PZ+/fXXbBWduG3q378/pkyZgt27d5vB4DYGEyNGjDDBRunSpc1+zJ49G5dffrlf3p+IiIiISDALmsBi0KBBZrZsVoVq0qQJGjdubHLN1q1bh40bN6JPnz5mnYLq0qWLGVmfGwYXrh566CFzExERERGRIB5jQUw/mj59Oho1amSCifXr15sA4+OPPzY3EREREREpGUHTY2FjzwRvIiIiIiISOIKqx0JERERERAKTAgsREREREfGZAgsREREREfGZAgsREREREQmfwKJGjRq4++678fXXX5s5JUREREREJHAETWDx0UcfIT4+Hvfeey8qVaqE6667DlOnTsXBgwdLetdERERERMJe0AQWnNDu5ZdfxqZNm7B48WKce+65mDhxIqpXr24eGz9+PDZv3lzSuykiIiIiEpaCJrBw1bx5c4wcORJLlizBtm3bcPPNN+P7779Hy5Yt0aJFC8yePbukd1FEREREJKwE3QR57qpWrYqBAwea2/Hjx/HNN98gNja2pHdLRERERCSsBH1g4YpjMK655pqS3g0RERERkbATlKlQIiIiIiISWBRYiIiIiIiIzxRYiIiIiIiIz0IisMjKysKXX36Jq6++uqR3RUREREQkLAV1YME5LVh2tlatWujTp09J746IiIiISNgKuqpQaWlp+PTTT/Huu++aeSxOnTplJse7/fbbUbZs2ZLePRERERGRsBQ0PRZLly7FoEGDUK1aNbz++uu49tprsX37dkRGRqJbt24KKkRERERESlDQ9Fh07NgRQ4cONQFG48aNS3p3REREREQkGAOLiy++2KQ/7du3D/369UOPHj0QERFR0rslIiIiIiLBlAo1b948/PHHH6a34u6770b16tVx3333mccUYIiIiIiIlKygCSyodu3aeOKJJ7BlyxZMnTrV9F5ERUXhqquuwqOPPooVK1aU9C6KiIiIiISloAosXF1yySX4+OOPsWvXLjP24uuvv0bbtm1LerdERERERMJS0AYWtgoVKpjAYuXKlVi2bFlJ746IiIiISFgKmsHb9gzbU6ZMweeff46///7bjK2oV68e/vWvf5kB3eeee25J76KIiIiISFgKmsDCsixceeWVmDNnDs4++2y0bNnSLFu3bh1uvfVWE2x88cUXJb2bIiIiIkFl27Zt2L9/v/m5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", "text/plain": [ "
" ] @@ -495,7 +517,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e33bdd78-8f0c-4690-9153-3f9eaffd4afa", + "id": "f9b7c128-6d30-425c-87db-645ea8cda143", "metadata": {}, "outputs": [], "source": [] diff --git a/src/ClusterFinderCUDA.test.cu b/src/ClusterFinderCUDA.test.cu index caba093..2c5703f 100644 --- a/src/ClusterFinderCUDA.test.cu +++ b/src/ClusterFinderCUDA.test.cu @@ -257,18 +257,20 @@ void feed_pedestal( int main(int argc, char *argv[]) { // Parse arguments + // const char *default_pedestal = + // "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/Calibration_CZT/" + // "2025Sept_m694/Sn25300eV/500_us_voltage_40kV/" + // "250922_CZTonly_Pedestal_Tp15C_tint_500_master_0.json"; const char *default_pedestal = - "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/Calibration_CZT/" - "2025Sept_m694/Sn25300eV/500_us_voltage_40kV/" - "250922_CZTonly_Pedestal_Tp15C_tint_500_master_0.json"; - // const char* default_pedestal = - // "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/November2025/sparse/dynamic/si_pedestal_200keV_DYNAMIC_tint_20us_master_0.json"; + "/mnt/sls_det_storage/matterhorn_data/aare_test_data/Moench03new/" + "cu_half_speed_master_4.json"; + // const char *default_data = + // "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/Calibration_CZT/" + // "2025Sept_m694/Sn25300eV/500_us_voltage_40kV/" + // "250922_CZTonly_Xray_Tp15C_tint_500_master_0.json"; const char *default_data = - "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/Calibration_CZT/" - "2025Sept_m694/Sn25300eV/500_us_voltage_40kV/" - "250922_CZTonly_Xray_Tp15C_tint_500_master_0.json"; - // const char* default_data = - // "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/November2025/sparse/dynamic/si_data_200keV_DYNAMIC_tint_20us_master_0.json"; + "/mnt/sls_det_storage/matterhorn_data/aare_test_data/Moench03new/" + "cu_half_speed_master_4.json"; std::filesystem::path pedestal_path(argc > 1 ? argv[1] : default_pedestal); std::filesystem::path data_path(argc > 2 ? argv[2] : default_data); @@ -283,7 +285,7 @@ int main(int argc, char *argv[]) { } // Defaults: Adjust depending on the test dataset used - size_t n_pedestal_frames = 6000; + size_t n_pedestal_frames = 1000; size_t n_data_frames = 10000; double nSigma = 5.0; @@ -381,6 +383,7 @@ int main(int argc, char *argv[]) { // Pre-read all frames into memory to remove I/O from timing std::vector> frames; frames.reserve(use_data); + data_file.seek(n_pedestal_frames); for (size_t i = 0; i < use_data; ++i) { auto f = data_file.read_frame(); // Copy into NDArray for consistent access @@ -440,6 +443,7 @@ int main(int argc, char *argv[]) { size_t gpu_total_clusters = 0; // --- Single frame on a single CUDA stream --- + /* { aare::ClusterFinderCUDA cuda_cf( {ROWS, COLS}, nSigma); @@ -465,56 +469,68 @@ int main(int argc, char *argv[]) { printf("GPU: %zu clusters in %.1f ms (%.2f ms/frame)\n", gpu_total_clusters, gpu_time, gpu_time / use_data); } + */ // --- Batched H2D + multi-stream (enable this block and disable the one // above to benchmark the batched path against the CPU results) --- - /* { - constexpr size_t BATCH_SIZE = 100; - constexpr int N_STREAMS = 2; + constexpr size_t BATCH_SIZE = 2000; + constexpr int N_STREAMS = 5; aare::ClusterFinderCUDA cuda_cf( - {ROWS, COLS}, nSigma, 1'000'000, N_STREAMS); + {ROWS, COLS}, nSigma, 4096, N_STREAMS); feed_pedestal(cuda_cf, pedestal_frames); - // Contiguous staging buffer reused across batches + // Contiguous staging buffer reused across batches — registered as + // pinned so that H2D transfers run at DMA bandwidth (~22 GB/s) instead + // of going through the CUDA driver's internal staging (~15 GB/s for + // pageable memory). std::vector batch_buffer(BATCH_SIZE * ROWS * COLS); + cuda_cf.register_input_buffer(batch_buffer.data(), + batch_buffer.size() * sizeof(FRAME_TYPE)); const size_t n_batches = (use_data + BATCH_SIZE - 1) / BATCH_SIZE; + double pack_ms = 0.0, gpu_ms = 0.0; Timer t; - t.start(); for (size_t bi = 0; bi < n_batches; ++bi) { - const size_t offset = bi * BATCH_SIZE; + const size_t offset = bi * BATCH_SIZE; const size_t actual_batch = std::min(BATCH_SIZE, use_data - offset); + t.start(); pack_frame_batch(frames, offset, actual_batch, batch_buffer); + pack_ms += t.elapsed_ms(); aare::NDView batch_view( batch_buffer.data(), {static_cast(actual_batch), ROWS, COLS}); - auto batch_results = cuda_cf.find_clusters_batched(batch_view, - offset); + t.start(); + auto batch_results = + cuda_cf.find_clusters_batched(batch_view, offset); + gpu_ms += t.elapsed_ms(); for (size_t f = 0; f < actual_batch; ++f) { - auto& cv = batch_results[f]; - auto& out = gpu_results[offset + f]; + auto &cv = batch_results[f]; + auto &out = gpu_results[offset + f]; out.clear(); out.reserve(cv.size()); - for (size_t j = 0; j < cv.size(); ++j) out.push_back(cv[j]); + for (size_t j = 0; j < cv.size(); ++j) + out.push_back(cv[j]); gpu_total_clusters += out.size(); } } - double gpu_time = t.elapsed_ms(); - printf("GPU(batched): %zu clusters in %.1f ms (%.2f ms/frame, batch=%zu, - streams=%d)\n", gpu_total_clusters, gpu_time, gpu_time / use_data, - BATCH_SIZE, N_STREAMS); + cuda_cf.unregister_input_buffer(); + + printf("GPU(batched): %zu clusters — pack=%.1f ms GPU=%.1f ms " + "total=%.1f ms" + " (%.2f µs/frame, batch=%zu, streams=%d)\n", + gpu_total_clusters, pack_ms, gpu_ms, pack_ms + gpu_ms, + 1000.0 * (pack_ms + gpu_ms) / use_data, BATCH_SIZE, N_STREAMS); } - */ // ========================================================================= // Phase 5: Comparison @@ -579,10 +595,10 @@ int main(int argc, char *argv[]) { // ========================================================================= // Phase 6: Per-frame timing benchmark // ========================================================================= - printf("\n--- Phase 6: Detailed timing (%d iterations) ---\n", 1000); + printf("\n--- Phase 6: Detailed timing (%d iterations) ---\n", 10000); if (use_data > 0) { - const int N_ITER = 1000; + const int N_ITER = 10000; const auto &bench_frame = frames[0]; // CPU benchmark @@ -614,7 +630,7 @@ int main(int argc, char *argv[]) { // Warmup cuda_cf.find_clusters(bench_frame.view(), 0); - cuda_cf.steal_clusters(true); + // cuda_cf.steal_clusters(true); Timer t; t.start(); @@ -629,18 +645,21 @@ int main(int argc, char *argv[]) { // --- GPU benchmark (batched + multi-streamed) --- { - constexpr size_t BATCH_SIZE = 500; - constexpr int N_STREAMS = 10; + constexpr size_t BATCH_SIZE = 2000; + constexpr int N_STREAMS = 5; aare::ClusterFinderCUDA - cuda_cf({ROWS, COLS}, nSigma, 1'000'000, N_STREAMS); + cuda_cf({ROWS, COLS}, nSigma, 4096, N_STREAMS); feed_pedestal(cuda_cf, pedestal_frames); - // Build one contiguous batch of BATCH_SIZE copies of bench_frame + // Build one contiguous batch of BATCH_SIZE copies of bench_frame. + // Register as pinned for DMA-speed H2D transfers. std::vector batch(BATCH_SIZE * ROWS * COLS); for (size_t k = 0; k < BATCH_SIZE; ++k) std::memcpy(batch.data() + k * ROWS * COLS, bench_frame.data(), ROWS * COLS * sizeof(FRAME_TYPE)); + cuda_cf.register_input_buffer(batch.data(), + batch.size() * sizeof(FRAME_TYPE)); aare::NDView batch_view( batch.data(), {static_cast(BATCH_SIZE), ROWS, COLS}); @@ -657,11 +676,16 @@ int main(int argc, char *argv[]) { const size_t n_iter_batches = (N_ITER + BATCH_SIZE - 1) / BATCH_SIZE; + std::vector> batch_results; + Timer t; t.start(); for (size_t b = 0; b < n_iter_batches; ++b) { - (void)cuda_cf.find_clusters_batched(batch_view, b * BATCH_SIZE); + batch_results = + cuda_cf.find_clusters_batched(batch_view, b * BATCH_SIZE); } + cuda_cf.unregister_input_buffer(); + printf("GPU(batched): %.3f ms/frame (H2D + kernel + D2H, " "batch=%zu, streams=%d)\n", t.elapsed_ms() / (n_iter_batches * BATCH_SIZE), BATCH_SIZE, diff --git a/src/ClusterFinderCUDA_old.test.cu b/src/ClusterFinderCUDA_old.test.cu new file mode 100644 index 0000000..f98d224 --- /dev/null +++ b/src/ClusterFinderCUDA_old.test.cu @@ -0,0 +1,683 @@ +// SPDX-License-Identifier: MPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aare/ClusterFinder.hpp" +#include "aare/ClusterFinderCUDA_old.hpp" +#include "aare/File.hpp" +#include "aare/Frame.hpp" +#include "aare/NDArray.hpp" +#include "aare/Pedestal.hpp" +#include "aare/defs.hpp" +#include "aare/utils/batch.hpp" + +// _____________ +// +// Timing helper +// _____________ +struct Timer { + using clock = std::chrono::high_resolution_clock; + clock::time_point t0; + + void start() { t0 = clock::now(); } + + double elapsed_ms() const { + return std::chrono::duration(clock::now() - t0) + .count(); + } +}; + +// __________________ +// +// Cluster comparison +// __________________ +template struct ClusterComparison { + size_t cpu_count = 0; + size_t gpu_count = 0; + size_t matched = 0; + size_t position_mismatch = 0; + size_t data_mismatch = 0; + size_t cpu_only = 0; + size_t gpu_only = 0; +}; + +// Sort clusters by (y, x) for deterministic comparison +template +void sort_clusters(std::vector &clusters) { + std::sort(clusters.begin(), clusters.end(), + [](const ClusterType &a, const ClusterType &b) { + if (a.y != b.y) + return a.y < b.y; + return a.x < b.x; + }); +} + +// Compare two sorted cluster lists +template +ClusterComparison +compare_clusters(std::vector &cpu_clusters, + std::vector &gpu_clusters) { + sort_clusters(cpu_clusters); + sort_clusters(gpu_clusters); + + ClusterComparison result; + result.cpu_count = cpu_clusters.size(); + result.gpu_count = gpu_clusters.size(); + + size_t ci = 0, gi = 0; + while (ci < cpu_clusters.size() && gi < gpu_clusters.size()) { + const auto &cc = cpu_clusters[ci]; + const auto &gc = gpu_clusters[gi]; + + if (cc.y == gc.y && cc.x == gc.x) { + // Same position/check data + bool data_ok = true; + constexpr int N = + ClusterType::cluster_size_x * ClusterType::cluster_size_y; + for (int k = 0; k < N; ++k) { + // if (cc.data[k] != gc.data[k]) { // a bit too strict + // espacially that pedestal update is slightly different + if (std::abs(cc.data[k] - gc.data[k]) > 5) { + data_ok = false; + break; + } + } + if (data_ok) + result.matched++; + else + result.data_mismatch++; + ci++; + gi++; + } else if (cc.y < gc.y || (cc.y == gc.y && cc.x < gc.x)) { + result.cpu_only++; + ci++; + } else { + result.gpu_only++; + gi++; + } + } + result.cpu_only += (cpu_clusters.size() - ci); + result.gpu_only += (gpu_clusters.size() - gi); + + return result; +} + +// ________________ +// +// Cluster printing +// ________________ +template +void print_cluster_comparison( + const std::vector> &cpu_results, + const std::vector> &gpu_results, + size_t max_per_frame = 10, size_t max_frames = 100) { + constexpr int NX = ClusterType::cluster_size_x; + constexpr int NY = ClusterType::cluster_size_y; + constexpr int N = NX * NY; + + size_t frames_shown = 0; + for (size_t fi = 0; fi < cpu_results.size() && frames_shown < max_frames; + ++fi) { + if (cpu_results[fi].empty() && gpu_results[fi].empty()) + continue; + + size_t n_cpu = cpu_results[fi].size(); + size_t n_gpu = gpu_results[fi].size(); + printf("\n Frame %zu: CPU=%zu clusters, GPU=%zu clusters\n", fi, n_cpu, + n_gpu); + + // Merge-walk over sorted lists (assumes already sorted by y,x) + size_t ci = 0, gi = 0; + size_t shown = 0; + while ((ci < n_cpu || gi < n_gpu) && shown < max_per_frame) { + bool have_cpu = ci < n_cpu; + bool have_gpu = gi < n_gpu; + + // Determine if current entries match in position + bool same_pos = have_cpu && have_gpu && + cpu_results[fi][ci].x == gpu_results[fi][gi].x && + cpu_results[fi][ci].y == gpu_results[fi][gi].y; + + if (same_pos) { + const auto &cc = cpu_results[fi][ci]; + const auto &gc = gpu_results[fi][gi]; + + // Check if data differs + bool differs = false; + for (int k = 0; k < N; ++k) { + if (cc.data[k] != gc.data[k]) { + differs = true; + break; + } + } + + printf(" CPU and GPU clusters found at SAME position " + "(col=%3d, row=%3d)\n %s\n", + cc.x, cc.y, differs ? "DATA MISMATCH" : "MATCH"); + printf(" CPU: ["); + for (int k = 0; k < N; ++k) { + if (k) + printf(", "); + printf("%6d", static_cast(cc.data[k])); + } + printf("]\n"); + if (differs) { + printf(" GPU: ["); + for (int k = 0; k < N; ++k) { + if (k) + printf(", "); + printf("%6d", static_cast(gc.data[k])); + } + printf("]\n"); + printf(" diff: ["); + for (int k = 0; k < N; ++k) { + if (k) + printf(", "); + int d = static_cast(gc.data[k]) - + static_cast(cc.data[k]); + printf("%+6d", d); + } + printf("]\n"); + } + ci++; + gi++; + shown++; + } else if (!have_gpu || + (have_cpu && + (cpu_results[fi][ci].y < gpu_results[fi][gi].y || + (cpu_results[fi][ci].y == gpu_results[fi][gi].y && + cpu_results[fi][ci].x < gpu_results[fi][gi].x)))) { + const auto &cc = cpu_results[fi][ci]; + printf(" (%3d, %3d) CPU ONLY\n", cc.x, cc.y); + printf(" CPU: ["); + for (int k = 0; k < N; ++k) { + if (k) + printf(", "); + printf("%6d", static_cast(cc.data[k])); + } + printf("]\n"); + ci++; + shown++; + } else { + const auto &gc = gpu_results[fi][gi]; + printf(" (%3d, %3d) GPU ONLY\n", gc.x, gc.y); + printf(" GPU: ["); + for (int k = 0; k < N; ++k) { + if (k) + printf(", "); + printf("%6d", static_cast(gc.data[k])); + } + printf("]\n"); + gi++; + shown++; + } + } + frames_shown++; + } +} + +// _________________________________________ +// +// Helpers for the updated (CPU-parity) API +// _________________________________________ + +// Copy a ClusterVector into a std::vector for downstream +// comparison code that expects the latter. +template +void drain_into(Finder &f, std::vector &out) { + auto cv = f.steal_clusters(true); + out.clear(); + out.reserve(cv.size()); + for (size_t j = 0; j < cv.size(); ++j) + out.push_back(cv[j]); +} + +// Feed a set of cached pedestal frames through any finder exposing the +// CPU-style push_pedestal_frame(NDView) method. Works for both +// ClusterFinder and ClusterFinderCUDA. +template +void feed_pedestal( + Finder &f, const std::vector> &ped_frames) { + for (const auto &frame : ped_frames) { + f.push_pedestal_frame(frame.view()); + } +} + +// ____________ +// +// Main +// ____________ +int main(int argc, char *argv[]) { + + // Parse arguments + // const char *default_pedestal = + // "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/Calibration_CZT/" + // "2025Sept_m694/Sn25300eV/500_us_voltage_40kV/" + // "250922_CZTonly_Pedestal_Tp15C_tint_500_master_0.json"; + const char *default_pedestal = + "/mnt/sls_det_storage/matterhorn_data/aare_test_data/Moench03new/" + "cu_half_speed_master_4.json"; + // const char *default_data = + // "/mnt/sls_det_storage/highZ_data/CZT_Vienna/Khalil/Calibration_CZT/" + // "2025Sept_m694/Sn25300eV/500_us_voltage_40kV/" + // "250922_CZTonly_Xray_Tp15C_tint_500_master_0.json"; + const char *default_data = + "/mnt/sls_det_storage/matterhorn_data/aare_test_data/Moench03new/" + "cu_half_speed_master_4.json"; + + std::filesystem::path pedestal_path(argc > 1 ? argv[1] : default_pedestal); + std::filesystem::path data_path(argc > 2 ? argv[2] : default_data); + + if (!std::filesystem::exists(pedestal_path)) { + fprintf(stderr, "Pedestal file not found: %s\n", pedestal_path.c_str()); + return 1; + } + if (!std::filesystem::exists(data_path)) { + fprintf(stderr, "Data file not found: %s\n", data_path.c_str()); + return 1; + } + + // Defaults: Adjust depending on the test dataset used + size_t n_pedestal_frames = 1000; + size_t n_data_frames = 10000; + double nSigma = 5.0; + + if (argc > 3) + n_pedestal_frames = std::atol(argv[3]); + if (argc > 4) + n_data_frames = std::atol(argv[4]); + if (argc > 5) + nSigma = std::atof(argv[5]); + + // Detector geometry from master file + constexpr uint8_t cs_x = 3; + constexpr uint8_t cs_y = 3; + using ClusterType = aare::Cluster; + using FRAME_TYPE = uint16_t; + using PEDESTAL_TYPE = double; + + // Read actual frame dimensions from the pedestal file + ssize_t ROWS, COLS; + { + aare::File probe(pedestal_path, "r"); + auto first_frame = probe.read_frame(); + ROWS = first_frame.rows(); + COLS = first_frame.cols(); + } + + printf("=== Cluster Finder: CPU vs CUDA (OLD) ===\n"); + printf("Detector: %zu x %zu\n", ROWS, COLS); + printf("Cluster: %d x %d\n", ClusterType::cluster_size_x, + ClusterType::cluster_size_y); + printf("nSigma: %.1f\n", nSigma); + + // ========================================================================= + // Phase 1: Build pedestal from dark frames (sanity check only + frame + // cache) + // ========================================================================= + // + // Neither ClusterFinder nor ClusterFinderCUDA needs an external Pedestal + // object; both build their own via push_pedestal_frame. We still read the + // pedestal file once, but cache the frames in memory so every subsequent + // finder can be fed without re-hitting disk. We also build a standalone + // Pedestal purely to print a sanity check. + // ========================================================================= + printf("\n--- Phase 1: Pedestal accumulation (sanity check + cache) ---\n"); + + std::vector> pedestal_frames; + aare::Pedestal pedestal(ROWS, COLS, 1000); + + { + aare::File ped_file(pedestal_path, "r"); + size_t total_ped = ped_file.total_frames(); + size_t use_ped = + (n_pedestal_frames == 0 || n_pedestal_frames > total_ped) + ? total_ped + : n_pedestal_frames; + printf("Pedestal frames: %zu / %zu\n", use_ped, total_ped); + + pedestal_frames.reserve(use_ped); + + Timer t; + t.start(); + + for (size_t i = 0; i < use_ped; ++i) { + auto frame = ped_file.read_frame(); + auto view = frame.view(); + + // Copy into a standalone NDArray that we can reuse as many times + // as we have finders to feed. + aare::NDArray arr({ROWS, COLS}); + for (ssize_t r = 0; r < ROWS; ++r) + for (ssize_t c = 0; c < COLS; ++c) + arr(r, c) = view(r, c); + pedestal_frames.push_back(std::move(arr)); + + pedestal.push_no_update(view); + } + pedestal.update_mean(); + + printf("Pedestal read+cached+built in %.1f ms\n", t.elapsed_ms()); + } + + printf("Pedestal mean[0,0] = %.2f, std[0,0] = %.4f\n", pedestal.mean(0, 0), + pedestal.std(0, 0)); + + // ========================================================================= + // Phase 2: Read data frames + // ========================================================================= + printf("\n--- Phase 2: Read data frames ---\n"); + + aare::File data_file(data_path, "r"); + size_t total_data = data_file.total_frames(); + size_t use_data = std::min(n_data_frames, total_data); + printf("Data frames: %zu / %zu\n", use_data, total_data); + + // Pre-read all frames into memory to remove I/O from timing + std::vector> frames; + frames.reserve(use_data); + data_file.seek(n_pedestal_frames); + for (size_t i = 0; i < use_data; ++i) { + auto f = data_file.read_frame(); + // Copy into NDArray for consistent access + aare::NDArray arr({ROWS, COLS}); + auto view = f.view(); + for (size_t r = 0; r < ROWS; ++r) + for (size_t c = 0; c < COLS; ++c) + arr(r, c) = view(r, c); + frames.push_back(std::move(arr)); + } + printf("Frames loaded into memory\n"); + + // ========================================================================= + // Phase 3: Sequential (CPU) cluster finding + // ========================================================================= + printf("\n--- Phase 3: CPU ClusterFinder ---\n"); + + std::vector> cpu_results(use_data); + size_t cpu_total_clusters = 0; + + { + // Build a ClusterFinder with the same pedestal + aare::ClusterFinder cf( + {ROWS, COLS}, nSigma); + + feed_pedestal(cf, pedestal_frames); + + Timer t; + t.start(); + + for (size_t i = 0; i < use_data; ++i) { + cf.find_clusters(frames[i].view(), static_cast(i)); + drain_into(cf, cpu_results[i]); + cpu_total_clusters += cpu_results[i].size(); + } + + double cpu_time = t.elapsed_ms(); + printf("CPU: %zu clusters in %.1f ms (%.2f ms/frame)\n", + cpu_total_clusters, cpu_time, cpu_time / use_data); + } + + // ========================================================================= + // Phase 4: CUDA cluster finding + // ========================================================================= + // + // The API mirrors ClusterFinder: push_pedestal_frame to train, then + // find_clusters / steal_clusters for each frame. H2D transfer and kernel + // launch happen internally. + // + // Toggle between the single-stream and batched paths by swapping which + // block is enabled. They both write into gpu_results so only one at a + // time makes sense. + // ========================================================================= + printf("\n--- Phase 4: CUDA ClusterFinder ---\n"); + + std::vector> gpu_results(use_data); + size_t gpu_total_clusters = 0; + + // --- Single frame on a single CUDA stream --- + /* + { + aare::ClusterFinderCUDA cuda_cf( + {ROWS, COLS}, nSigma); + + feed_pedestal(cuda_cf, pedestal_frames); + + // Warmup: first CUDA call pays driver/context init overhead. The + // pedestal drifts slightly during this single frame, which is + // acceptable for timing purposes. + cuda_cf.find_clusters(frames[0].view(), 0); + cuda_cf.steal_clusters(true); + + Timer t; + t.start(); + + for (size_t i = 0; i < use_data; ++i) { + cuda_cf.find_clusters(frames[i].view(), static_cast(i)); + drain_into(cuda_cf, gpu_results[i]); + gpu_total_clusters += gpu_results[i].size(); + } + + double gpu_time = t.elapsed_ms(); + printf("GPU: %zu clusters in %.1f ms (%.2f ms/frame)\n", + gpu_total_clusters, gpu_time, gpu_time / use_data); + } + */ + + // --- Batched H2D + multi-stream (enable this block and disable the one + // above to benchmark the batched path against the CPU results) --- + { + constexpr size_t BATCH_SIZE = 2000; + constexpr int N_STREAMS = 5; + + aare::ClusterFinderCUDA cuda_cf( + {ROWS, COLS}, nSigma, 50'000, N_STREAMS); + + feed_pedestal(cuda_cf, pedestal_frames); + + // Contiguous staging buffer reused across batches + std::vector batch_buffer(BATCH_SIZE * ROWS * COLS); + + const size_t n_batches = (use_data + BATCH_SIZE - 1) / BATCH_SIZE; + + double pack_ms = 0.0, gpu_ms = 0.0; + Timer t; + + for (size_t bi = 0; bi < n_batches; ++bi) { + const size_t offset = bi * BATCH_SIZE; + const size_t actual_batch = std::min(BATCH_SIZE, use_data - offset); + + t.start(); + pack_frame_batch(frames, offset, actual_batch, batch_buffer); + pack_ms += t.elapsed_ms(); + + aare::NDView batch_view( + batch_buffer.data(), + {static_cast(actual_batch), ROWS, COLS}); + + t.start(); + auto batch_results = + cuda_cf.find_clusters_batched(batch_view, offset); + gpu_ms += t.elapsed_ms(); + + for (size_t f = 0; f < actual_batch; ++f) { + auto &cv = batch_results[f]; + auto &out = gpu_results[offset + f]; + out.clear(); + out.reserve(cv.size()); + for (size_t j = 0; j < cv.size(); ++j) + out.push_back(cv[j]); + gpu_total_clusters += out.size(); + } + } + + printf("GPU(batched): %zu clusters — pack=%.1f ms GPU=%.1f ms " + "total=%.1f ms" + " (%.2f µs/frame, batch=%zu, streams=%d)\n", + gpu_total_clusters, pack_ms, gpu_ms, pack_ms + gpu_ms, + 1000.0 * (pack_ms + gpu_ms) / use_data, BATCH_SIZE, N_STREAMS); + } + + // ========================================================================= + // Phase 5: Comparison + // ========================================================================= + printf("\n--- Phase 5: Comparison ---\n"); + + size_t total_matched = 0; + size_t total_data_mismatch = 0; + size_t total_cpu_only = 0; + size_t total_gpu_only = 0; + size_t frames_with_differences = 0; + + for (size_t i = 0; i < use_data; ++i) { + auto result = compare_clusters(cpu_results[i], gpu_results[i]); + + total_matched += result.matched; + total_data_mismatch += result.data_mismatch; + total_cpu_only += result.cpu_only; + total_gpu_only += result.gpu_only; + + bool has_diff = (result.cpu_only > 0 || result.gpu_only > 0 || + result.data_mismatch > 0); + if (has_diff) { + frames_with_differences++; + // Print details for first few mismatching frames + if (frames_with_differences <= 5) { + printf(" Frame %zu: CPU=%zu GPU=%zu matched=%zu " + "data_mismatch=%zu cpu_only=%zu gpu_only=%zu\n", + i, result.cpu_count, result.gpu_count, result.matched, + result.data_mismatch, result.cpu_only, result.gpu_only); + } + } + } + + printf("\nSummary over %zu frames:\n", use_data); + printf(" CPU total clusters: %zu\n", cpu_total_clusters); + printf(" GPU total clusters: %zu\n", gpu_total_clusters); + printf(" Matched: %zu\n", total_matched); + printf(" Data mismatch: %zu\n", total_data_mismatch); + printf(" CPU only: %zu\n", total_cpu_only); + printf(" GPU only: %zu\n", total_gpu_only); + printf(" Frames with diffs: %zu / %zu\n", frames_with_differences, + use_data); + + if (total_cpu_only == 0 && total_gpu_only == 0 && + total_data_mismatch == 0) { + printf("\n*** PASS: CPU and GPU results match exactly ***\n"); + } else { + printf("\n*** DIFFERENCES DETECTED ***\n"); + } + + // // Print detailed cluster comparison (side-by-side with diffs) + // if (cpu_total_clusters > 0 || gpu_total_clusters > 0) { + // size_t max_clusters_per_frame = 10; + // size_t max_frames = 100; + // printf("\n--- Cluster details (up to %zu frames, %zu clusters each) + // ---\n", max_frames, max_clusters_per_frame); + // print_cluster_comparison(cpu_results, gpu_results, + // max_clusters_per_frame, max_frames); + // } + + // ========================================================================= + // Phase 6: Per-frame timing benchmark + // ========================================================================= + printf("\n--- Phase 6: Detailed timing (%d iterations) ---\n", 10000); + + if (use_data > 0) { + const int N_ITER = 10000; + const auto &bench_frame = frames[0]; + + // CPU benchmark + { + aare::ClusterFinder cf( + {ROWS, COLS}, nSigma); + + // Load pedestal + feed_pedestal(cf, pedestal_frames); + + // Warmup + cf.find_clusters(bench_frame.view(), 0); + cf.steal_clusters(true); + + Timer t; + t.start(); + for (int iter = 0; iter < N_ITER; ++iter) { + cf.find_clusters(bench_frame.view(), 0); + cf.steal_clusters(true); + } + double cpu_per_frame = t.elapsed_ms() / N_ITER; + printf("CPU: %.3f ms/frame\n", cpu_per_frame); + } + // --- GPU benchmark (single frame, single stream) --- + { + aare::ClusterFinderCUDA + cuda_cf({ROWS, COLS}, nSigma); + feed_pedestal(cuda_cf, pedestal_frames); + + // Warmup + cuda_cf.find_clusters(bench_frame.view(), 0); + cuda_cf.steal_clusters(true); + + Timer t; + t.start(); + for (int iter = 0; iter < N_ITER; ++iter) { + cuda_cf.find_clusters(bench_frame.view(), 0); + cuda_cf.steal_clusters(true); + } + printf("GPU: %.3f ms/frame (H2D + kernel + D2H, single " + "frame, single stream)\n", + t.elapsed_ms() / N_ITER); + } + + // --- GPU benchmark (batched + multi-streamed) --- + { + constexpr size_t BATCH_SIZE = 2000; + constexpr int N_STREAMS = 5; + + aare::ClusterFinderCUDA + cuda_cf({ROWS, COLS}, nSigma, 50'000, N_STREAMS); + feed_pedestal(cuda_cf, pedestal_frames); + + // Build one contiguous batch of BATCH_SIZE copies of bench_frame + std::vector batch(BATCH_SIZE * ROWS * COLS); + for (size_t k = 0; k < BATCH_SIZE; ++k) + std::memcpy(batch.data() + k * ROWS * COLS, bench_frame.data(), + ROWS * COLS * sizeof(FRAME_TYPE)); + + aare::NDView batch_view( + batch.data(), {static_cast(BATCH_SIZE), ROWS, COLS}); + + // Warmup. The kernel mutates the device-side pedestal for every + // non-photon pixel, so after a 500-frame warmup the pedestal + // state has drifted. Reset to a clean baseline by clearing the + // host pedestal and re-feeding the cached frames; this also + // re-arms the dirty flag so the next find_clusters re-uploads. + (void)cuda_cf.find_clusters_batched(batch_view, 0); + cuda_cf.clear_pedestal(); + feed_pedestal(cuda_cf, pedestal_frames); + + const size_t n_iter_batches = + (N_ITER + BATCH_SIZE - 1) / BATCH_SIZE; + + Timer t; + t.start(); + for (size_t b = 0; b < n_iter_batches; ++b) { + (void)cuda_cf.find_clusters_batched(batch_view, b * BATCH_SIZE); + } + printf("GPU(batched): %.3f ms/frame (H2D + kernel + D2H, " + "batch=%zu, streams=%d)\n", + t.elapsed_ms() / (n_iter_batches * BATCH_SIZE), BATCH_SIZE, + N_STREAMS); + } + } + + printf("\nDone.\n"); + return 0; +} \ No newline at end of file diff --git a/src/Makefile b/src/Makefile index 196204c..bd67746 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,17 +7,24 @@ LDFLAGS := -L../build -L../build/_deps/fmt-build LIBS := -laare_core -lfmt -lstdc++fs DEFINES := -DAARE_LOG_LEVEL=logERROR -TARGET := test_cf_cuda -SRC := ClusterFinderCUDA.test.cu -DEP := $(SRC:.cu=.d) +TARGET_OLD := test_cf_cuda_old +TARGET := test_cf_cuda -all: $(TARGET) +SRC_OLD := ClusterFinderCUDA_old.test.cu +SRC := ClusterFinderCUDA.test.cu + +DEP := $(SRC:.cu=.d) $(SRC_OLD:.cu=.d) + +all: $(TARGET) $(TARGET_OLD) $(TARGET): $(SRC) ../include/aare/clusterfinder_kernel.cuh $(NVCC) -Xptxas=-v $(ARCH) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(LDFLAGS) $< -o $@ $(LIBS) +$(TARGET_OLD): $(SRC_OLD) ../include/aare/clusterfinder_kernel.cuh + $(NVCC) -Xptxas=-v $(ARCH) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(LDFLAGS) $< -o $@ $(LIBS) + clean: - rm -f $(TARGET) $(DEP) + rm -f $(TARGET) $(TARGET_OLD) $(DEP) -include $(DEP)