Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-26 08:22:10

0001 /** TRACCC library, part of the ACTS project (R&D line)
0002  *
0003  * (c) 2025 CERN for the benefit of the ACTS project
0004  *
0005  * Mozilla Public License Version 2.0
0006  */
0007 
0008 // Project include(s).
0009 #include "traccc/definitions/math.hpp"
0010 #include "traccc/utils/pair.hpp"
0011 
0012 // Local include(s).
0013 #include "../../utils/barrier.hpp"
0014 #include "../../utils/global_index.hpp"
0015 #include "remove_tracks.cuh"
0016 
0017 // VecMem include(s).
0018 #include <vecmem/containers/device_vector.hpp>
0019 #include <vecmem/containers/jagged_device_vector.hpp>
0020 
0021 // Thrust include(s).
0022 #include <thrust/binary_search.h>
0023 #include <thrust/count.h>
0024 #include <thrust/execution_policy.h>
0025 #include <thrust/find.h>
0026 
0027 namespace traccc::cuda::kernels {
0028 
0029 __device__ __forceinline__ uint64_t pack_key(uint32_t meas, uint32_t thr) {
0030   return (uint64_t(meas) << 32) | uint64_t(thr);
0031 }
0032 __device__ __forceinline__ void unpack_key(uint64_t k, uint32_t& meas,
0033                                            uint32_t& thr) {
0034   meas = uint32_t(k >> 32);
0035   thr = uint32_t(k & 0xFFFFFFFFu);
0036 }
0037 
0038 __device__ void count_tracks(int tid, int* sh_n_meas, int n_tracks,
0039                              unsigned int& bound, unsigned int& count) {
0040   unsigned int add = 0;
0041 
0042   // --- Warp-level phase: handle strides < 32 using warp shuffle (no
0043   // __syncthreads needed) ---
0044   const int lane = threadIdx.x & 31;
0045   const unsigned int full_mask = 0xFFFFFFFFu;
0046 
0047   // Load this thread's value into a register if it's in range
0048   int v = (tid < n_tracks) ? sh_n_meas[tid] : 0;
0049 
0050   // Mask for active lanes in this warp
0051   const unsigned int mask = __ballot_sync(full_mask, tid < n_tracks);
0052 
0053   const int max_stride = min(n_tracks, 32);
0054 
0055   for (int stride = 1; stride < max_stride; stride <<= 1) {
0056     // Accumulate neighbor's value via warp shuffle
0057     unsigned int other = __shfl_down_sync(mask, v, stride);
0058     if (lane + stride < 32 && (tid + stride) < n_tracks) {
0059       v += other;
0060     }
0061 
0062     // Thread 0 can directly check its register value in the warp phase
0063     if (tid == 0) {
0064       if (v < bound) {
0065         add = stride << 1;
0066       }
0067     }
0068   }
0069 
0070   // Write warp-phase result back to shared memory
0071   if (tid < n_tracks) {
0072     sh_n_meas[tid] = static_cast<int>(v);
0073   }
0074   __syncthreads();
0075 
0076   // --- Block-level phase: handle strides >= 32 (minimal required
0077   // synchronizations) ---
0078   for (int stride = 32; stride < n_tracks; stride <<= 1) {
0079     if ((tid + stride) < n_tracks) {
0080       sh_n_meas[tid] += sh_n_meas[tid + stride];
0081     }
0082     __syncthreads();
0083 
0084     if (tid == 0 && sh_n_meas[0] < bound) {
0085       add = stride << 1;
0086     }
0087     __syncthreads();
0088   }
0089 
0090   // --- Final update ---
0091   if (tid == 0) {
0092     count += add;
0093   }
0094   __syncthreads();
0095 }
0096 
0097 __launch_bounds__(512) __global__
0098     void remove_tracks(device::remove_tracks_payload payload) {
0099   if (threadIdx.x == 0) {
0100     if (*(payload.max_shared) == 0) {
0101       *(payload.terminate) = 1;
0102     }
0103     // Reset the max_shared and n_updated_tracks
0104     *(payload.max_shared) = 0;
0105     *(payload.n_updated_tracks) = 0;
0106   }
0107 
0108   __syncthreads();
0109 
0110   if (*(payload.terminate) == 1) {
0111     return;
0112   }
0113 
0114   __shared__ int sh_buffer[512];
0115   __shared__ measurement_id_type sh_meas_ids[512];
0116   __shared__ unsigned int sh_threads[512];
0117   __shared__ uint64_t sh_keys[512];
0118   __shared__ unsigned int n_meas_total;
0119   __shared__ unsigned int bound;
0120   __shared__ unsigned int n_tracks_to_iterate;
0121   __shared__ unsigned int min_thread;
0122   __shared__ unsigned int N;
0123   __shared__ unsigned int n_updating_threads;
0124 
0125   auto threadIndex = threadIdx.x;
0126 
0127   int gid = static_cast<int>(*payload.n_accepted) - 1 - threadIndex;
0128   sh_buffer[threadIndex] = 0;
0129   sh_meas_ids[threadIndex] = std::numeric_limits<measurement_id_type>::max();
0130   sh_threads[threadIndex] = std::numeric_limits<unsigned int>::max();
0131 
0132   vecmem::device_vector<const unsigned int> sorted_ids(payload.sorted_ids_view);
0133   vecmem::jagged_device_vector<const measurement_id_type> meas_ids(
0134       payload.meas_ids_view);
0135   vecmem::device_vector<const unsigned int> n_meas(payload.n_meas_view);
0136   vecmem::device_vector<const unsigned int> meas_id_to_unique_id(
0137       payload.meas_id_to_unique_id_view);
0138   vecmem::jagged_device_vector<const unsigned int> tracks_per_measurement(
0139       payload.tracks_per_measurement_view);
0140   vecmem::jagged_device_vector<int> track_status_per_measurement(
0141       payload.track_status_per_measurement_view);
0142   vecmem::device_vector<unsigned int> n_accepted_tracks_per_measurement(
0143       payload.n_accepted_tracks_per_measurement_view);
0144   vecmem::device_vector<unsigned int> n_shared(payload.n_shared_view);
0145   vecmem::device_vector<traccc::scalar> rel_shared(payload.rel_shared_view);
0146   vecmem::device_vector<unsigned int> updated_tracks(
0147       payload.updated_tracks_view);
0148   vecmem::device_vector<int> is_updated(payload.is_updated_view);
0149   vecmem::device_vector<int> track_count(payload.track_count_view);
0150 
0151   if (threadIndex == 0) {
0152     *(payload.n_removable_tracks) = 0;
0153     *(payload.n_meas_to_remove) = 0;
0154     *(payload.n_valid_threads) = 0;
0155     n_meas_total = 0;
0156     bound = 512;
0157     N = 1;
0158     n_tracks_to_iterate = 0;
0159     min_thread = std::numeric_limits<unsigned int>::max();
0160   }
0161 
0162   __syncthreads();
0163 
0164   unsigned int trk_id = 0;
0165   unsigned int n_m = 0;
0166   if (gid >= 0) {
0167     trk_id = sorted_ids[gid];
0168     n_m = n_meas[trk_id];
0169 
0170     // Buffer for the number of measurement per track
0171     sh_buffer[threadIndex] = n_m;
0172   }
0173 
0174   __syncthreads();
0175 
0176   auto n_tracks_total = min(bound, *payload.n_accepted);
0177 
0178   /****************************************
0179    * Count the number of removable tracks
0180    ****************************************/
0181 
0182   count_tracks(threadIdx.x, sh_buffer, n_tracks_total, bound,
0183                n_tracks_to_iterate);
0184 
0185   if (threadIndex == 0 && n_tracks_to_iterate == 0) {
0186     n_tracks_to_iterate = 1;
0187   }
0188 
0189   // @TODO: Improve the logic
0190   if (threadIndex < n_tracks_to_iterate && gid >= 0) {
0191     const unsigned int pos = atomicAdd(&n_meas_total, n_m);
0192 
0193     const auto& mids = meas_ids[trk_id];
0194     for (int i = 0; i < n_m; i++) {
0195       sh_meas_ids[pos + i] = mids[i];
0196       sh_threads[pos + i] = threadIndex;
0197     }
0198   }
0199 
0200   __syncthreads();
0201 
0202   // Bitonic sort on meas_to_thread w.r.t. measurement id
0203   if (threadIndex == 0) {
0204     N = (n_meas_total == 0) ? 1 : 1 << (32 - __clz(n_meas_total - 1));
0205   }
0206   __syncthreads();
0207 
0208   const auto tid = threadIndex;
0209   // No early return: out-of-range threads carry a sentinel and only
0210   // sync/shuffle.
0211   uint64_t key = (tid < N) ? pack_key(sh_meas_ids[tid], sh_threads[tid])
0212                            : 0xFFFFFFFFFFFFFFFFull;  // sentinel that won't
0213                                                      // affect in-range items
0214 
0215   for (int k = 2; k <= N; k <<= 1) {
0216     // Inter-warp (j >= 32): use shared + barriers
0217     for (int j = (k >> 1); j >= warpSize; j >>= 1) {
0218       sh_keys[tid] = key;  // safe: sh_keys sized to blockDim.x
0219       __syncthreads();
0220 
0221       const int ixj = tid ^ j;
0222       // If partner is out-of-range, compare with self (no change).
0223       uint64_t other = (ixj < N) ? sh_keys[ixj] : key;
0224 
0225       const bool dir = ((tid & k) == 0);    // ascending segment?
0226       const bool lower = ((tid & j) == 0);  // am I lower index?
0227 
0228       const uint64_t mn = (key < other) ? key : other;
0229       const uint64_t mx = (key < other) ? other : key;
0230 
0231       key = dir ? (lower ? mn : mx) : (lower ? mx : mn);
0232 
0233       __syncthreads();
0234     }
0235 
0236     // Intra-warp (j < 32): warp shuffles only; no barriers
0237     for (int j = min(k >> 1, warpSize >> 1); j > 0; j >>= 1) {
0238       const unsigned mask = 0xFFFFFFFFu;
0239       uint64_t other = __shfl_xor_sync(mask, key, j);
0240 
0241       const bool dir = ((tid & k) == 0);
0242       const bool lower = ((tid & j) == 0);
0243 
0244       const uint64_t mn = (key < other) ? key : other;
0245       const uint64_t mx = (key < other) ? other : key;
0246 
0247       key = dir ? (lower ? mn : mx) : (lower ? mx : mn);
0248     }
0249 
0250     // Commit for next inter-warp round visibility
0251     sh_keys[tid] = key;
0252     __syncthreads();
0253   }
0254 
0255   // Write back only in-range threads
0256   if (tid < N) {
0257     uint32_t meas, thr;
0258     unpack_key(key, meas, thr);
0259     sh_meas_ids[tid] = meas;
0260     sh_threads[tid] = thr;
0261   }
0262 
0263   // Find starting point
0264   if (threadIndex < n_meas_total) {
0265     auto mid = sh_meas_ids[threadIndex];
0266     bool is_start = (threadIndex == 0) || (sh_meas_ids[threadIndex - 1] != mid);
0267     const auto unique_meas_idx = meas_id_to_unique_id.at(mid);
0268     const auto its_accepted_tracks =
0269         n_accepted_tracks_per_measurement.at(unique_meas_idx);
0270 
0271     if (is_start) {
0272       int i = threadIndex + 1;
0273       int n_sharing_tracks = 1;
0274 
0275       while (i < n_meas_total && sh_meas_ids[i] == mid) {
0276         if (sh_threads[i] != sh_threads[i - 1]) {
0277           n_sharing_tracks++;
0278 
0279           if (n_sharing_tracks == its_accepted_tracks) {
0280             atomicMin(&min_thread, sh_threads[i - 1]);
0281             break;
0282           }
0283         }
0284         i++;
0285       }
0286     }
0287   }
0288 
0289   __syncthreads();
0290 
0291   if (threadIndex == 0) {
0292     if (min_thread == 0) {
0293       *(payload.n_removable_tracks) = 1;
0294     } else if (min_thread == std::numeric_limits<unsigned int>::max()) {
0295       *(payload.n_removable_tracks) = n_tracks_to_iterate;
0296     } else {
0297       *(payload.n_removable_tracks) = min_thread;
0298     }
0299   }
0300 
0301   __syncthreads();
0302 
0303   auto meas_to_remove_temp = sh_meas_ids[threadIndex];
0304   auto threads_temp = sh_threads[threadIndex];
0305 
0306   __syncthreads();
0307 
0308   if (threadIndex == 0) {
0309     *(payload.n_meas_to_remove) = n_meas_total;
0310   }
0311 
0312   __syncthreads();
0313 
0314   int is_valid = (threads_temp < *(payload.n_removable_tracks)) ? 1 : 0;
0315 
0316   // TODO: Use better reduction algorithm
0317   if (is_valid) {
0318     atomicAdd(payload.n_valid_threads, 1);
0319   }
0320 
0321   __syncthreads();
0322 
0323   // Exclusive scan (Hillis-Steele)
0324 
0325   // Buffer for the prefix
0326   sh_buffer[threadIndex] = is_valid;  // copy input
0327   __syncthreads();
0328 
0329   for (int offset = 1; offset < *(payload.n_meas_to_remove); offset <<= 1) {
0330     int val = 0;
0331     if (threadIndex >= offset) {
0332       val = sh_buffer[threadIndex - offset];
0333     }
0334     __syncthreads();
0335     sh_buffer[threadIndex] += val;
0336     __syncthreads();
0337   }
0338 
0339   if (is_valid) {
0340     sh_buffer[threadIndex] -= 1;
0341     sh_meas_ids[sh_buffer[threadIndex]] = meas_to_remove_temp;
0342     sh_threads[sh_buffer[threadIndex]] = threads_temp;
0343   }
0344 
0345   __syncthreads();
0346 
0347   meas_to_remove_temp = sh_meas_ids[threadIndex];
0348   threads_temp = sh_threads[threadIndex];
0349 
0350   /********************
0351    * Remove tracks
0352    ********************/
0353 
0354   __syncthreads();
0355 
0356   bool is_valid_thread = false;
0357   bool is_duplicate = true;
0358 
0359   const unsigned n_accepted_prev = *(payload.n_accepted);
0360 
0361   __syncthreads();
0362 
0363   if (threadIndex == 0) {
0364     (*payload.n_accepted) -= *(payload.n_removable_tracks);
0365     n_updating_threads = 0;
0366   }
0367 
0368   if (threadIndex < *(payload.n_valid_threads)) {
0369     sh_meas_ids[threadIndex] = meas_to_remove_temp;
0370     sh_threads[threadIndex] = threads_temp;
0371     is_valid_thread = true;
0372   }
0373 
0374   __syncthreads();
0375 
0376   if (is_valid_thread) {
0377     const auto id = sh_meas_ids[threadIndex];
0378     is_duplicate = (threadIndex > 0 && sh_meas_ids[threadIndex - 1] == id);
0379   }
0380 
0381   // Buffer for the track ids
0382   sh_buffer[threadIndex] = std::numeric_limits<int>::max();
0383 
0384   bool active = false;
0385   unsigned int pos1;
0386   int alive_trk_id = 0;
0387 
0388   if (!is_duplicate && is_valid_thread) {
0389     const auto id = sh_meas_ids[threadIndex];
0390     const auto unique_meas_idx = meas_id_to_unique_id.at(id);
0391 
0392     // If there is only one track associated with measurement, the
0393     // number of shared measurement can be reduced by one
0394     const auto& tracks = tracks_per_measurement[unique_meas_idx];
0395     auto track_status = track_status_per_measurement[unique_meas_idx];
0396 
0397     auto trk_id = sorted_ids.at(n_accepted_prev - 1 - sh_threads[threadIndex]);
0398 
0399     unsigned int worst_idx =
0400         thrust::lower_bound(thrust::seq, tracks.begin(), tracks.end(), trk_id) -
0401         tracks.begin();
0402 
0403     track_status[worst_idx] = 0;
0404 
0405     int n_sharing_tracks = 1;
0406     for (unsigned int i = threadIndex + 1; i < *(payload.n_valid_threads);
0407          ++i) {
0408       if (sh_meas_ids[i] == id && sh_threads[i] != sh_threads[i - 1]) {
0409         n_sharing_tracks++;
0410 
0411         trk_id = sorted_ids[n_accepted_prev - 1 - sh_threads[i]];
0412 
0413         worst_idx = thrust::lower_bound(thrust::seq, tracks.begin(),
0414                                         tracks.end(), trk_id) -
0415                     tracks.begin();
0416 
0417         track_status[worst_idx] = 0;
0418 
0419       } else if (sh_meas_ids[i] != id) {
0420         break;
0421       }
0422     }
0423 
0424     vecmem::device_atomic_ref<unsigned int> n_accepted_per_meas(
0425         n_accepted_tracks_per_measurement.at(
0426             static_cast<unsigned int>(unique_meas_idx)));
0427     const unsigned int N_A = n_accepted_per_meas.fetch_sub(n_sharing_tracks);
0428 
0429     if (N_A == 1 + n_sharing_tracks) {
0430       active = true;
0431       const unsigned int alive_idx =
0432           thrust::find(thrust::seq, track_status.begin(), track_status.end(),
0433                        1) -
0434           track_status.begin();
0435 
0436       pos1 = atomicAdd(&n_updating_threads, 1);
0437       alive_trk_id = static_cast<int>(tracks[alive_idx]);
0438 
0439       sh_buffer[pos1] = alive_trk_id;
0440       atomicAdd(&track_count[alive_trk_id], 1);
0441 
0442       const auto m_count = static_cast<unsigned int>(
0443           thrust::count(thrust::seq, meas_ids[alive_trk_id].begin(),
0444                         meas_ids[alive_trk_id].end(), id));
0445 
0446       const unsigned int N_S =
0447           vecmem::device_atomic_ref<unsigned int>(n_shared.at(alive_trk_id))
0448               .fetch_sub(m_count);
0449     }
0450   }
0451 
0452   __syncthreads();
0453 
0454   if (active) {
0455     auto count = atomicAdd(&track_count[alive_trk_id], -1);
0456     if (count == 1) {
0457       // Write updated track IDs
0458       vecmem::device_atomic_ref<unsigned int> num_updated_tracks(
0459           *(payload.n_updated_tracks));
0460 
0461       const unsigned int pos2 = num_updated_tracks.fetch_add(1);
0462 
0463       updated_tracks[pos2] = alive_trk_id;
0464       is_updated[alive_trk_id] = 1;
0465 
0466       rel_shared.at(alive_trk_id) = math::div_ieee754(
0467           static_cast<traccc::scalar>(n_shared.at(alive_trk_id)),
0468           static_cast<traccc::scalar>(n_meas.at(alive_trk_id)));
0469     }
0470   }
0471 }
0472 
0473 }  // namespace traccc::cuda::kernels