1+ #include < algorithm>
2+ #include < numeric>
13#include < random>
4+ #include < stdexcept>
25#include < unordered_set>
36
47#include < Kokkos_Bitset.hpp>
58#include < Kokkos_Core.hpp>
69#include < Kokkos_NestedSort.hpp>
10+
11+ #ifdef KOKKOS_ENABLE_CUDA
12+ #include < cub/cub.cuh>
13+ #endif
714#include < boost/math/distributions/binomial.hpp>
815#include < pcg_random.hpp>
916
1623namespace edm
1724{
1825
19- void full_sort (TmpDistances distances, TmpIndices indices, int n_lib,
20- int n_pred, int n_partial, int Tp)
26+ void full_sort_kokkos (TmpDistances distances, TmpIndices indices, int n_lib,
27+ int n_pred, int n_partial, int Tp)
2128{
2229 Kokkos::parallel_for (
2330 " EDM::ccm::sort" , Kokkos::TeamPolicy<>(n_pred, Kokkos::AUTO ),
@@ -37,6 +44,26 @@ void full_sort(TmpDistances distances, TmpIndices indices, int n_lib,
3744 });
3845}
3946
47+ void full_sort (TmpDistances distances, TmpIndices indices, int n_lib,
48+ int n_pred, int n_partial, int Tp)
49+ {
50+ bool use_scratch =
51+ ScratchDistances1D::shmem_size (distances.extent (1 )) +
52+ ScratchIndices1D::shmem_size (indices.extent (1 )) <
53+ Kokkos::TeamPolicy<>(n_pred, Kokkos::AUTO ).scratch_size_max (0 );
54+
55+ if (use_scratch) {
56+ full_sort_with_scratch (distances, indices, n_lib, n_pred, n_partial,
57+ Tp);
58+ } else {
59+ #ifdef KOKKOS_ENABLE_CUDA
60+ full_sort_radix (distances, indices, n_lib, n_pred, n_partial, Tp);
61+ #else
62+ full_sort_kokkos (distances, indices, n_lib, n_pred, n_partial, Tp);
63+ #endif
64+ }
65+ }
66+
4067void full_sort_with_scratch (TmpDistances distances, TmpIndices indices,
4168 int n_lib, int n_pred, int n_partial, int Tp)
4269{
@@ -74,6 +101,102 @@ void full_sort_with_scratch(TmpDistances distances, TmpIndices indices,
74101 });
75102}
76103
104+ void full_sort_cpu (TmpDistances distances, TmpIndices indices, int n_lib,
105+ int n_pred, int n_partial, int Tp)
106+ {
107+ auto distances_h =
108+ Kokkos::create_mirror_view_and_copy (Kokkos::HostSpace (), distances);
109+ auto indices_h = Kokkos::create_mirror_view (Kokkos::HostSpace (), indices);
110+
111+ Kokkos::parallel_for (
112+ " EDM::ccm::full_sort_cpu" ,
113+ Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(0 , n_pred),
114+ [=](int i) {
115+ float *dist_row = &distances_h (i, 0 );
116+ int *ind_row = &indices_h (i, 0 );
117+
118+ std::iota (ind_row, ind_row + n_lib, 0 );
119+
120+ std::sort (ind_row, ind_row + n_lib, [dist_row](int a, int b) {
121+ return dist_row[a] < dist_row[b];
122+ });
123+
124+ std::vector<float > sorted_dist (n_lib);
125+ for (int j = 0 ; j < n_lib; j++) {
126+ sorted_dist[j] = std::sqrt (dist_row[ind_row[j]]);
127+ }
128+
129+ for (int j = 0 ; j < n_lib; j++) {
130+ distances_h (i, j) = sorted_dist[j];
131+ indices_h (i, j) = ind_row[j] + n_partial + Tp;
132+ }
133+ });
134+
135+ Kokkos::deep_copy (distances, distances_h);
136+ Kokkos::deep_copy (indices, indices_h);
137+ }
138+
139+ void full_sort_radix (TmpDistances distances, TmpIndices indices, int n_lib,
140+ int n_pred, int n_partial, int Tp)
141+ {
142+ #ifdef KOKKOS_ENABLE_CUDA
143+ // Initialize: apply sqrt and set indices
144+ Kokkos::parallel_for (
145+ " EDM::ccm::radix_init" , Kokkos::TeamPolicy<>(n_pred, Kokkos::AUTO ),
146+ KOKKOS_LAMBDA (const Kokkos::TeamPolicy<>::member_type &member) {
147+ int row = member.league_rank ();
148+ Kokkos::parallel_for (
149+ Kokkos::TeamThreadRange (member, n_lib), [=](int j) {
150+ distances (row, j) = sqrt (distances (row, j));
151+ indices (row, j) = j + n_partial + Tp;
152+ });
153+ });
154+
155+ // Create segment offsets array: [0, n_lib, 2*n_lib, ..., n_pred*n_lib]
156+ Kokkos::View<int *, DevSpace> offsets (" offsets" , n_pred + 1 );
157+ Kokkos::parallel_for (
158+ " EDM::ccm::init_offsets" , n_pred + 1 ,
159+ KOKKOS_LAMBDA (int i) { offsets (i) = i * n_lib; });
160+
161+ // Allocate temporary buffers for double-buffering
162+ TmpDistances dist_temp (" dist_temp" , n_pred, n_lib);
163+ TmpIndices idx_temp (" idx_temp" , n_pred, n_lib);
164+
165+ // Set up double buffers for CUB
166+ cub::DoubleBuffer<float > d_keys (distances.data (), dist_temp.data ());
167+ cub::DoubleBuffer<int > d_values (indices.data (), idx_temp.data ());
168+
169+ // Determine temporary storage requirements
170+ size_t temp_storage_bytes = 0 ;
171+ cub::DeviceSegmentedRadixSort::SortPairs (
172+ nullptr , temp_storage_bytes, d_keys, d_values, n_pred * n_lib, n_pred,
173+ offsets.data (), offsets.data () + 1 );
174+
175+ // Allocate temporary storage
176+ Kokkos::View<char *, DevSpace> temp_storage (" temp_storage" ,
177+ temp_storage_bytes);
178+
179+ // Run sorting operation
180+ cub::DeviceSegmentedRadixSort::SortPairs (
181+ temp_storage.data (), temp_storage_bytes, d_keys, d_values,
182+ n_pred * n_lib, n_pred, offsets.data (), offsets.data () + 1 );
183+
184+ // Copy results back if needed (CUB may have swapped buffers)
185+ if (d_keys.Current () != distances.data ()) {
186+ Kokkos::deep_copy (distances, dist_temp);
187+ Kokkos::deep_copy (indices, idx_temp);
188+ }
189+ #else
190+ (void )distances;
191+ (void )indices;
192+ (void )n_lib;
193+ (void )n_pred;
194+ (void )n_partial;
195+ (void )Tp;
196+ throw std::runtime_error (" full_sort_radix requires CUDA" );
197+ #endif
198+ }
199+
77200const unsigned int RADIX_BITS = 8 ;
78201const unsigned int RADIX_SIZE = 1 << RADIX_BITS ;
79202const unsigned int RADIX_MASK = RADIX_SIZE - 1 ;
@@ -194,6 +317,46 @@ void partial_sort(TmpDistances distances, TmpIndices indices, int k, int n_lib,
194317 });
195318}
196319
320+ void partial_sort_cpu (TmpDistances distances, TmpIndices indices, int k,
321+ int n_lib, int n_pred, int n_partial, int Tp)
322+ {
323+ auto distances_h =
324+ Kokkos::create_mirror_view_and_copy (Kokkos::HostSpace (), distances);
325+ auto indices_h = Kokkos::create_mirror_view (Kokkos::HostSpace (), indices);
326+
327+ Kokkos::parallel_for (
328+ " EDM::ccm::partial_sort_cpu" ,
329+ Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(0 , n_pred),
330+ [=](int i) {
331+ float *dist_row = &distances_h (i, 0 );
332+ int *ind_row = &indices_h (i, 0 );
333+
334+ std::iota (ind_row, ind_row + n_lib, 0 );
335+
336+ std::partial_sort (
337+ ind_row, ind_row + k, ind_row + n_lib,
338+ [dist_row](int a, int b) { return dist_row[a] < dist_row[b]; });
339+
340+ std::vector<float > topk_dist (k);
341+ for (int j = 0 ; j < k; j++) {
342+ topk_dist[j] = std::sqrt (dist_row[ind_row[j]]);
343+ }
344+
345+ for (int j = 0 ; j < k; j++) {
346+ distances_h (i, j) = topk_dist[j];
347+ indices_h (i, j) = ind_row[j] + n_partial + Tp;
348+ }
349+
350+ for (int j = k; j < n_lib; j++) {
351+ distances_h (i, j) = FLT_MAX ;
352+ indices_h (i, j) = -1 ;
353+ }
354+ });
355+
356+ Kokkos::deep_copy (distances, distances_h);
357+ Kokkos::deep_copy (indices, indices_h);
358+ }
359+
197360std::vector<float > ccm (TimeSeries lib, TimeSeries target,
198361 const std::vector<int > &lib_sizes, int sample, int E,
199362 int tau, int Tp, int seed, float accuracy)
@@ -217,15 +380,6 @@ std::vector<float> ccm(TimeSeries lib, TimeSeries target,
217380 // Compute pairwise distance matrix
218381 calc_distances (lib, lib, tmp_dist, n_lib, n_pred, E, tau);
219382
220- bool use_scratch =
221- #ifdef KOKKOS_ENABLE_CUDA
222- ScratchDistances1D::shmem_size (tmp_dist.extent (1 )) +
223- ScratchIndices1D::shmem_size (tmp_ind.extent (1 )) <
224- Kokkos::TeamPolicy<>(n_pred, Kokkos::AUTO ).scratch_size_max (0 );
225- #else
226- false ;
227- #endif
228-
229383 // (Partially) Sort each row of the distance matrix
230384 if (accuracy < 1 .0f ) {
231385 // Calculate the probability of a row to be sampled
@@ -245,12 +399,7 @@ std::vector<float> ccm(TimeSeries lib, TimeSeries target,
245399
246400 partial_sort (tmp_dist, tmp_ind, k, n_lib, n_pred, n_partial, Tp);
247401 } else {
248- if (use_scratch) {
249- full_sort_with_scratch (tmp_dist, tmp_ind, n_lib, n_pred, n_partial,
250- Tp);
251- } else {
252- full_sort (tmp_dist, tmp_ind, n_lib, n_pred, n_partial, Tp);
253- }
402+ full_sort (tmp_dist, tmp_ind, n_lib, n_pred, n_partial, Tp);
254403 }
255404
256405 pcg32 rng;
0 commit comments