Project
Loading...
Searching...
No Matches
GPUTPCCFCheckPadBaseline.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14
16#include "CfArray2D.h"
17#include "PackedCharge.h"
18#include "clusterFinderDefs.h"
20#include "GPUCommonAlgorithm.h"
21
22#ifndef GPUCA_GPUCODE
23#include "MCLabelAccumulator.h"
24#include "utils/VcShim.h"
25#include <vector>
26#endif
27
28#if 0
29#define DPRINT(...) printf(__VA_ARGS__)
30#define DPRINTB(...) \
31 if (iThread == 0) \
32 printf(__VA_ARGS__)
33#define DPRINTB_IF(test, ...) \
34 if (iThread == 0 && (test)) \
35 printf(__VA_ARGS__)
36#else
37#define DPRINT(...) ((void)0)
38#define DPRINTB(...) ((void)0)
39#define DPRINTB_IF(test, ...) ((void)0)
40#endif
41
42using namespace o2::gpu;
43using namespace o2::gpu::tpccf;
44
46
47static GPUdi() HIPTailDescriptor* GetHIPTails(GPUTPCClusterFinder& clusterer, int32_t row)
48{
49 // HIP TAILS: indexing starts at 1, so 0 index indicates no connection
50 return clusterer.mPhipTailsByRow + row * GPUTPCCFHIPClusterizer::MaxHIPTailsPerRow;
51}
52
53static GPUdi() Charge UpdateHIPTailFilter(Charge filteredCharge, Charge charge, Charge alpha)
54{
55 return filteredCharge + alpha * (charge - filteredCharge);
56}
57
58static GPUdi() float HIPTailTimeVariance(const HIPTailDescriptor& tail)
59{
60 const float length = tail.tailEnd > tail.tailStart ? float(tail.tailEnd - tail.tailStart) : 1.f;
61 return (length * length - 1.f) * (1.f / 12.f);
62}
63
64// Collect tails marked for closing across the workgroup using a prefix scan,
65// then cooperatively zero the charge map entries for each closed tail.
66// Caller must set acc.activeHIPTail.end before calling if the tail is open.
67static GPUdi() uint16_t CloseHIPTails(
68 Kernel::GPUSharedMemory& smem,
69 GPUTPCClusterFinder& clusterer,
70 int32_t iThread, int32_t nThreads,
71 int16_t iPadHandle,
72 CfChargePos basePos,
73 CfArray2D<PackedCharge>& chargeMap,
74 Kernel::PadChargeAccu& acc,
75 bool shouldCloseTail)
76{
77 const uint32_t row = basePos.row();
78 const uint16_t nClosedTails = work_group_count(shouldCloseTail);
79
80 auto* nHIPTails = clusterer.mPnHIPTails;
81 auto* hipTails = GetHIPTails(clusterer, row);
82
83 if (nClosedTails > 0) {
84 int16_t iClosedTail = work_group_scan_inclusive_add((int16_t)shouldCloseTail) - 1;
85 const bool shouldStoreTail = shouldCloseTail && acc.activeHIPTail.Length() > 0;
86 uint16_t nStoredTails = work_group_count(shouldStoreTail);
87 int16_t iStoredTail = work_group_scan_inclusive_add((int16_t)shouldStoreTail) - 1;
88
89 // Use exactly one atomic add per closing call to reduce differences in
90 // tail ordering between runs.
91 if (nStoredTails > 0) {
92 if (iThread == 0) {
93 smem.tailStoreBase = CAMath::AtomicAdd(&nHIPTails[row], (uint32_t)nStoredTails);
94 }
95 GPUbarrier();
96 }
97 if (shouldCloseTail) {
98 smem.tailsClosedPad[iClosedTail] = iPadHandle;
99 smem.tailsClosed[iClosedTail] = acc.activeHIPTail;
100 smem.tailsClosedStoreIdx[iClosedTail] = GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow;
101
102 if (shouldStoreTail) {
103 const uint32_t idx = smem.tailStoreBase + iStoredTail + 1;
104 smem.tailsClosedStoreIdx[iClosedTail] = idx;
106 hipTails[idx] = {0, 0, (uint16_t)iPadHandle,
107 (uint16_t)acc.activeHIPTail.start, (uint16_t)acc.activeHIPTail.end,
108 acc.activeSatStart, acc.activeSatEnd,
109 0.f, 0.f};
110 }
111 }
112
113 acc.tailFilterCharge = 0;
114 acc.activeHIPTail.Reset();
115 acc.activeSatStart = acc.activeSatEnd = -1;
116 }
117
118 GPUbarrier();
119 }
120
121 // TODO: performance improvement -> parallelize this loop across tails
122 for (uint16_t iTail = 0; iTail < nClosedTails; iTail++) {
123 const auto tailPad = smem.tailsClosedPad[iTail];
124 const auto tail = smem.tailsClosed[iTail];
125 const uint32_t tailStoreIdx = smem.tailsClosedStoreIdx[iTail];
126
127 Charge qTot = 0.f;
128 Charge qMax = 0.f;
129 for (uint16_t iTime = iThread; iTime < tail.Length(); iTime += nThreads) {
130 const int16_t time = tail.start + iTime;
131 auto pos = basePos.delta({tailPad, time});
132 const Charge q = chargeMap[pos].unpack();
133 qTot += q;
134 qMax = CAMath::Max(qMax, q);
135 chargeMap[pos] = PackedCharge{0};
136 }
137
138 smem.tailQTotScratch[iThread] = qTot;
139 smem.tailQMaxScratch[iThread] = qMax;
140 GPUbarrier();
141 for (uint16_t active = nThreads; active > 1;) {
142 const uint16_t stride = (active + 1) / 2;
143 if (iThread < active - stride) {
144 smem.tailQTotScratch[iThread] += smem.tailQTotScratch[iThread + stride];
145 smem.tailQMaxScratch[iThread] = CAMath::Max(smem.tailQMaxScratch[iThread], smem.tailQMaxScratch[iThread + stride]);
146 }
147 active = stride;
148 GPUbarrier();
149 }
150
151 if (iThread == 0 && tailStoreIdx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) {
152 HIPTailDescriptor& tailDescriptor = hipTails[tailStoreIdx];
153 tailDescriptor.qTot = smem.tailQTotScratch[0];
154 tailDescriptor.qMax = smem.tailQMaxScratch[0];
155 }
156 }
157
158 return nClosedTails;
159}
160
161template <bool CheckHIPTrigger, bool CheckHIPTailEnd>
162static GPUdi() void ScanCachedCharges(Kernel::GPUSharedMemory& smem, uint16_t timeOffset, uint16_t pad, Charge hipTailThreshold, Charge hipTailFilterAlpha, Kernel::PadChargeAccu& acc)
163{
164 for (int32_t i = 0; i < Kernel::NumOfCachedTBs; i++) {
165 const Charge qs = smem.charges[i][pad];
166 const int16_t curTB = timeOffset + i;
167
168 acc.totalCharges += qs > 0;
169 acc.consecCharges = qs > 0 ? acc.consecCharges + 1 : 0;
170 acc.maxConsecCharges = CAMath::Max(acc.consecCharges, acc.maxConsecCharges);
171 acc.maxCharge = CAMath::Max<Charge>(qs, acc.maxCharge);
172
173 if (qs >= hipTailThreshold) {
174 if (acc.aboveThresholdStart < 0) {
175 acc.aboveThresholdStart = curTB;
176 }
177 } else {
178 acc.aboveThresholdStart = -1;
179 }
180
181 if constexpr (CheckHIPTrigger) {
182 // Track saturated plateaus. A plateau continuing from the previous chunk always triggers there as well,
183 // so tracking them only in chunks with a trigger is sufficient.
184 const bool isSaturated = qs >= Charge(Kernel::MaxADC);
185 if (isSaturated && !acc.plateauOpen) {
186 acc.plateauStart = curTB;
187 }
188 acc.plateauOpen = isSaturated;
189 // Plateau of the active tail continues from a previous chunk
190 if (isSaturated && acc.activeSatStart > -1 && acc.activeSatStart == acc.plateauStart) {
191 acc.activeSatEnd = curTB;
192 }
193
194 if (acc.HIPtb < 0 && isSaturated) {
195 acc.HIPtb = acc.aboveThresholdStart; // start of rising edge, not first sat TB
196 acc.satStart = acc.plateauStart; // Plateau may have started in the previous chunk if it crosses the chunk boundary
197 smem.tails[pad] = {acc.HIPtb, 0}; // Broadcast HIP start TB to neighboring pads / threads
198 }
199 if (isSaturated && acc.satStart > -1 && acc.satStart == acc.plateauStart) {
200 acc.satEnd = curTB;
201 }
202 }
203
204 if constexpr (CheckHIPTailEnd) {
205 if (acc.activeHIPTail.IsOpen()) {
206 acc.tailFilterCharge = UpdateHIPTailFilter(acc.tailFilterCharge, qs, hipTailFilterAlpha);
207 if (acc.tailFilterCharge < hipTailThreshold) {
208 acc.activeHIPTail.end = curTB;
209 }
210 }
211 }
212 }
213}
214
215template <>
216GPUd() void GPUTPCCFCheckPadBaseline::Thread<0>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
217{
218#ifdef GPUCA_GPUCODE
219 CheckBaselineGPU(nBlocks, nThreads, iBlock, iThread, smem, clusterer);
220#else
221 CheckBaselineCPU(nBlocks, nThreads, iBlock, iThread, smem, clusterer);
222#endif
223}
224
225// Charges are stored in a 2D array (pad and time) using a tiling layout.
226// Tiles are 8 pads x 4 timebins large stored in time-major layout and make up a single cacheline.
227//
228// This kernel processes one row per block. Threads cooperatively load chunks
229// of 4 consecutive time bins for all pads into shared memory. Thread `i` then processes charges for pad `i` in shared memory.
230// Blocks require `nextMultipleOf<64>(138 * 4) = 576` threads to process the largest TPC rows with 138 pads correctly.
231GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
232{
233#if defined(GPUCA_GPUCODE) && !defined(GPUCA_DEVICE_SINGLE_THREAD) // Needs 576 threads per block, CPUDevice RTC uses the host-compiled kernel
234 static_assert(GPUCA_GET_THREAD_COUNT(GPUCA_LB_GPUTPCCFCheckPadBaseline) == 576);
235 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
236 return;
237 }
238
239 const CfFragment& frag = clusterer.mPmemory->frag;
240 const bool hipFilterOn = clusterer.Param().rec.tpc.hipTailFilter;
241 const Charge hipTailThreshold = clusterer.Param().rec.tpc.hipTailFilterThreshold;
242 const Charge hipTailFilterAlpha = clusterer.Param().rec.tpc.hipTailFilterAlpha;
243 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
244
245 constexpr GPUTPCGeometry geo;
246
247 const auto iRow = iBlock;
248 const auto nPads = geo.NPads(iRow);
249 const CfChargePos basePos{(Row)iRow, 0, 0};
250
251 PadChargeAccu acc;
252
253 const int16_t iPadOffset = iThread % MaxNPadsPerRow;
254 const int16_t iTimeOffset = iThread / MaxNPadsPerRow;
255 const int16_t iPadHandle = iThread;
256 const bool handlePad = iPadHandle < nPads;
257
258 if (iPadHandle < MaxNPadsPerRow) {
259 smem.tails[iPadHandle] = {-1, -1};
260 }
261 GPUbarrier();
262
263 // Pad filter scans the entire fragments including overlap.
264 // Minimal runtime overhead and prevents headaches later on as
265 // saturated signal in overlap region can create tails in the next fragment
266 // even when cleared in current fragment as they're decoded twice
267 const TPCFragmentTime firstTB = 0;
268 const TPCFragmentTime lastTB = frag.length;
269
270 for (uint16_t t = firstTB; t < lastTB; t += NumOfCachedTBs) {
271
272 bool thisThreadHasTrigger = false;
273 for (uint16_t tt = 0; tt < NumOfCachedTBs; tt += TimebinsPerCacheline) {
274 const TPCFragmentTime iTimeLoad = t + tt + iTimeOffset;
275
276 const CfChargePos pos = basePos.delta({iPadOffset, iTimeLoad});
277
278 const Charge ql = iTimeLoad < lastTB && iPadOffset < nPads ? chargeMap[pos].unpack() : 0;
279 smem.charges[tt + iTimeOffset][iPadOffset] = ql;
280
281 thisThreadHasTrigger |= ql >= Charge(MaxADC);
282 }
283
284 bool hasHIPTrigger = false;
285 if (hipFilterOn) {
286 hasHIPTrigger = work_group_any(thisThreadHasTrigger);
287 } else {
288 // Need a barrier here even if HIP filter is disabled
289 GPUbarrier();
290 }
291
292 acc.HIPtb = -1;
293 acc.satStart = acc.satEnd = -1;
294 if (!hasHIPTrigger) {
295 acc.plateauOpen = false; // No saturated TB in this chunk
296 }
297
298 if (handlePad) {
299
300 // TODO: is this really necessary?
301 // Why is the old version so much slower, when we just add short branches to the loop???
302 if (!hasHIPTrigger) [[likely]] {
303 if (!acc.activeHIPTail.IsOpen()) {
304 ScanCachedCharges<false, false>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
305 } else {
306 ScanCachedCharges<false, true>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
307 }
308 } else {
309 if (!acc.activeHIPTail.IsOpen()) {
310 ScanCachedCharges<true, false>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
311 } else {
312 ScanCachedCharges<true, true>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
313 }
314 }
315 }
316
317 GPUbarrier();
318
319 if (hasHIPTrigger) [[unlikely]] {
320
321 DPRINTB("%d: Trigger!\n", iBlock);
322
323 if (handlePad && acc.HIPtb < 0) {
324
325 // Search neighboring pads for trigger
326 for (int16_t i = -SSClusterPadWidth; i < 0; i++) {
327 const auto p = iPadHandle + i;
328 if (p > -1) {
329 acc.HIPtb = CAMath::Max(smem.tails[p].start, acc.HIPtb);
330 }
331 }
332
333 for (int16_t i = 1; i <= SSClusterPadWidth; i++) {
334 const auto p = iPadHandle + i;
335 if (p < MaxNPadsPerRow) {
336 acc.HIPtb = CAMath::Max(smem.tails[p].start, acc.HIPtb);
337 }
338 }
339 }
340
341 bool shouldCloseTail = acc.HIPtb > -1 && acc.activeHIPTail.HasValue();
342 // End the old tail at the rising edge of the new trigger, also if the tail filter already ended it later in this chunk.
343 // Otherwise the old tail would take the saturated samples of the new trigger.
344 if (shouldCloseTail && (acc.activeHIPTail.IsOpen() || acc.activeHIPTail.end > acc.HIPtb)) {
345 DPRINT("%d: end = %d\n", iThread, acc.HIPtb);
346 acc.activeHIPTail.end = acc.HIPtb;
347 }
348
349 // Tails are closed at the rising edge of the new trigger, which can lie before the saturated plateau of the old tail.
350 // The new tail inherits the plateau if it now contains saturated samples, the old one keeps it only if it still does.
351 int16_t newSatStart = acc.satStart;
352 int16_t newSatEnd = acc.satEnd;
353 if (shouldCloseTail && acc.activeSatStart > -1) {
354 if (acc.satStart < 0 && acc.activeSatEnd >= acc.activeHIPTail.end) {
355 newSatStart = acc.activeSatStart;
356 newSatEnd = acc.activeSatEnd;
357 }
358 if (acc.activeSatStart >= acc.activeHIPTail.end) {
359 acc.activeSatStart = acc.activeSatEnd = -1;
360 }
361 }
362
363 CloseHIPTails(smem, clusterer, iThread, nThreads, iPadHandle, basePos, chargeMap, acc, shouldCloseTail);
364
365 GPUbarrier();
366
367 if (acc.HIPtb > -1) {
368 DPRINT("%d: start = %d\n", iThread, acc.HIPtb);
369 acc.activeHIPTail.SetOpen(acc.HIPtb);
370 acc.tailFilterCharge = Charge(MaxADC);
371 acc.activeSatStart = newSatStart;
372 acc.activeSatEnd = newSatEnd;
373 }
374
375 // Clear smem between iterations to prevent stale entries
376 if (handlePad) {
377 smem.tails[iPadHandle].Reset();
378 }
379
380 GPUbarrier();
381
382 } // if (hasHIPTrigger)
383
384 } // for (uint16_t t = firstTB; t < lastTB; t += NumOfCachedTBs)
385
386 if (handlePad) {
387 updatePadBaseline(basePos.gpad + iPadHandle, clusterer, acc.totalCharges, acc.maxConsecCharges, acc.maxCharge);
388 }
389
390 // --- Close remaining tails
391 const bool shouldCloseTail = acc.activeHIPTail.HasValue();
392
393 // Call `work_group_any` here, instead of always counting.
394 // This is important as `work_group_count` is a lot slower
395 // and has a lot of overhead if no HIPs were found.
396 if (work_group_any(shouldCloseTail)) {
397 if (shouldCloseTail && acc.activeHIPTail.IsOpen()) {
398 acc.activeHIPTail.end = lastTB;
399 }
400
401 [[maybe_unused]] const uint16_t nClosedTails = CloseHIPTails(smem, clusterer, iThread, nThreads, iPadHandle, basePos, chargeMap, acc, shouldCloseTail);
402
403 DPRINTB_IF(nClosedTails > 0, "%d: Close remaining tails (%d)\n", iBlock, nClosedTails);
404 }
405
406#endif
407}
408
409GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
410{
411#ifndef GPUCA_GPUCODE
412 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
413 return;
414 }
415
416 constexpr GPUTPCGeometry geo;
417 const int32_t row = iBlock;
418 const int32_t nPads = geo.NPads(row);
419 const int32_t nVecPads = (nPads + PadsPerCacheline - 1) / PadsPerCacheline;
420
421 const CfFragment& frag = clusterer.mPmemory->frag;
422 const bool hipFilterOn = clusterer.Param().rec.tpc.hipTailFilter;
423 const Charge hipTailThreshold = clusterer.Param().rec.tpc.hipTailFilterThreshold;
424 const Charge hipTailFilterAlpha = clusterer.Param().rec.tpc.hipTailFilterAlpha;
425 auto* nHIPTails = clusterer.mPnHIPTails;
426 auto* hipTails = GetHIPTails(clusterer, row);
427
428 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
429
430 using UShort8 = Vc::fixed_size_simd<uint16_t, PadsPerCacheline>;
431 using Short8 = Vc::fixed_size_simd<int16_t, PadsPerCacheline>;
432 using Charge8 = Vc::fixed_size_simd<float, PadsPerCacheline>;
433
434 std::vector<UShort8> totalChargesV(nVecPads, UShort8{Vc::Zero});
435 std::vector<UShort8> consecChargesV(nVecPads, UShort8{Vc::Zero});
436 std::vector<UShort8> maxConsecChargesV(nVecPads, UShort8{Vc::Zero});
437 std::vector<Charge8> maxChargeV(nVecPads, Charge8{Vc::Zero});
438
439 std::vector<Short8> localHipTbV(nVecPads, -1);
440 std::vector<Short8> broadcastHipTbV(nVecPads, -1);
441 std::vector<Short8> localSatStartV(nVecPads, -1); // start of the saturated plateau that triggered in the current chunk, only set for pads with a local trigger
442 std::vector<Short8> localSatEndV(nVecPads, -1); // end of that plateau as far as seen in the current chunk
443 std::vector<Short8> plateauStartV(nVecPads, -1); // first TB of the current / last saturated plateau on this pad
444 std::vector<Short8> plateauOpenV(nVecPads, 0); // 1 while the previous TB was saturated
445 std::vector<Short8> aboveThresholdStartV(nVecPads, -1);
446 std::vector<Short8> activeHIPTailStartV(nVecPads, -1);
447 std::vector<Short8> activeHIPTailEndV(nVecPads, -1);
448 std::vector<Short8> activeHIPTailSatStartV(nVecPads, -1); // start of the saturated plateau that triggered the active tail, -1 if inherited from a neighbor
449 std::vector<Short8> activeHIPTailSatEndV(nVecPads, -1); // end of that plateau, extended while the plateau continues into later chunks
450 std::vector<Charge8> tailFilterChargeV(nVecPads, Charge8{Vc::Zero});
451
452 for (int16_t t = 0; t < frag.length; t += NumOfCachedTBs) {
453
454 bool hasAnyTrigger = false;
455
456 // Run actual noisy pad filter and look for HIP trigger
457 for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) {
458
459 auto totalCharges = totalChargesV[iVecPad];
460 auto consecCharges = consecChargesV[iVecPad];
461 auto maxConsecCharges = maxConsecChargesV[iVecPad];
462 auto maxCharge = maxChargeV[iVecPad];
463
464 auto hipTb = Short8(-1);
465 auto satStart = Short8(-1);
466 auto satEnd = Short8(-1);
467 auto plateauStart = plateauStartV[iVecPad];
468 auto plateauOpen = plateauOpenV[iVecPad];
469 const auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
470 auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
471 auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
472 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
473 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
474 auto tailFilterCharge = tailFilterChargeV[iVecPad];
475
476 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, t);
477
478 for (tpccf::TPCFragmentTime localtime = 0; localtime < NumOfCachedTBs; localtime++) {
479
480 const uint16_t* packedChargeStart = reinterpret_cast<uint16_t*>(&chargeMap[basePos.delta({0, localtime})]);
481 const UShort8 packedCharges = t + localtime < frag.length
482 ? UShort8{packedChargeStart, Vc::Aligned}
483 : UShort8{Vc::Zero};
484 const auto isCharge = packedCharges != 0;
485
486 const auto unpackedCharges = Charge8(packedCharges) / Charge(1 << PackedCharge::DecimalBits);
487
488 if (isCharge.isNotEmpty()) {
489 totalCharges(isCharge)++;
490 consecCharges += 1;
491 consecCharges(not isCharge) = 0;
492 maxConsecCharges = Vc::max(consecCharges, maxConsecCharges);
493
494 // Manually unpack charges to float.
495 // Duplicated from PackedCharge::unpack to generate vectorized code:
496 // Charge unpack() const { return Charge(mVal & ChargeMask) / Charge(1 << DecimalBits); }
497 // Note that PackedCharge has to cut off the highest 2 bits via ChargeMask as they are used for flags by the cluster finder
498 // and are not part of the charge value. We can skip this step because the cluster finder hasn't run yet
499 // and thus these bits are guarenteed to be zero.
500 maxCharge = Vc::max(maxCharge, unpackedCharges);
501
502 const auto aboveRisingEdge = unpackedCharges >= hipTailThreshold;
503 const auto startRisingEdge = aboveRisingEdge && aboveThresholdStart < 0;
504 aboveThresholdStart(startRisingEdge) = t + localtime;
505 aboveThresholdStart(!aboveRisingEdge) = -1;
506
507 // Track saturated plateaus across chunk boundaries
508 const auto isSaturated = unpackedCharges >= Charge(MaxADC);
509 plateauStart(isSaturated && plateauOpen < 1) = t + localtime;
510 plateauOpen = 0;
511 plateauOpen(isSaturated) = 1;
512 // Plateau of the active tail continues from a previous chunk
513 activeHIPTailSatEnd(isSaturated && activeHIPTailSatStart > -1 && (activeHIPTailSatStart - plateauStart) == 0) = t + localtime;
514
515 const auto hasNewTrigger = hipTb < 0 && isSaturated;
516 hipTb(hasNewTrigger) = aboveThresholdStart;
517 satStart(hasNewTrigger) = plateauStart; // Plateau may have started in the previous chunk if it crosses the chunk boundary
518 satEnd(isSaturated && satStart > -1 && (satStart - plateauStart) == 0) = t + localtime;
519 hasAnyTrigger |= hasNewTrigger.isNotEmpty();
520 } else {
521 consecCharges = 0;
522 aboveThresholdStart = -1;
523 plateauOpen = 0;
524 }
525
526 const auto tailOpen = activeHIPTailStart > -1 && activeHIPTailEnd < 0;
527 tailFilterCharge(tailOpen) = tailFilterCharge + hipTailFilterAlpha * (unpackedCharges - tailFilterCharge);
528 activeHIPTailEnd(tailOpen && tailFilterCharge < hipTailThreshold) = t + localtime;
529 } // for (tpccf::TPCFragmentTime localtime = 0; localtime < TimebinsPerCacheline; localtime++)
530
531 totalChargesV[iVecPad] = totalCharges;
532 consecChargesV[iVecPad] = consecCharges;
533 maxConsecChargesV[iVecPad] = maxConsecCharges;
534 maxChargeV[iVecPad] = maxCharge;
535
536 localHipTbV[iVecPad] = hipTb;
537 localSatStartV[iVecPad] = satStart;
538 localSatEndV[iVecPad] = satEnd;
539 plateauStartV[iVecPad] = plateauStart;
540 plateauOpenV[iVecPad] = plateauOpen;
541 activeHIPTailSatEndV[iVecPad] = activeHIPTailSatEnd;
542 aboveThresholdStartV[iVecPad] = aboveThresholdStart;
543 activeHIPTailStartV[iVecPad] = activeHIPTailStart;
544 activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
545 tailFilterChargeV[iVecPad] = tailFilterCharge;
546
547 } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
548
549 if (hasAnyTrigger) {
550 broadcastHipTbV = localHipTbV;
551 }
552
553 // Broadcast trigger times to neighboring pads across the whole
554 for (int16_t iVecPad = 0; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) {
555
556 const auto hipTb = localHipTbV[iVecPad];
557
558 const auto hasHipTrigger = hipTb > -1;
559 if (hasHipTrigger.isNotEmpty()) [[unlikely]] {
560
561 // TODO: This could be vectorised, but doesn't seem necessary
562 for (uint16_t p = 0; p < PadsPerCacheline; p++) {
563 if (hasHipTrigger[p]) {
564 const int16_t pad = iVecPad * PadsPerCacheline + p;
565 const int16_t neighborSt = CAMath::Max(0, pad - SSClusterPadWidth);
566 const int16_t neighborEnd = CAMath::Min(nPads, pad + SSClusterPadWidth + 1);
567 for (int16_t np = neighborSt; np < neighborEnd; np++) {
568 if (np == pad) {
569 continue;
570 }
571 const auto pv = np / PadsPerCacheline;
572 const auto pi = np % PadsPerCacheline;
573 // GPU keeps a pad's own trigger time; only pads without a local trigger inherit from neighbors.
574 if (localHipTbV[pv][pi] < 0) {
575 broadcastHipTbV[pv][pi] = CAMath::Max<int16_t>(hipTb[p], broadcastHipTbV[pv][pi]);
576 }
577 } // for (int16_t np = neighborSt; np < neighborEnd; np++)
578 } // if (hasHipTrigger[p]) {
579 } // for (uint16_t p = 0; p < PadsPerCacheline; p++)
580 } // if (hasHipTrigger.isNotEmpty())
581 } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
582
583 // Close old tails for all pads, open new tails in case of overlap
584 for (int16_t iVecPad = 0; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) {
585
586 auto hipTb = broadcastHipTbV[iVecPad];
587 const auto satStart = localSatStartV[iVecPad];
588 const auto satEnd = localSatEndV[iVecPad];
589 auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
590 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
591 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
592 auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
593 auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
594 auto tailFilterCharge = tailFilterChargeV[iVecPad];
595
596 const auto shouldCloseTail = hipTb > -1 && activeHIPTailStart > -1;
597 // End the old tail at the rising edge of the new trigger, also if the tail filter already ended it later in this chunk.
598 // Otherwise the old tail would take the saturated samples of the new trigger.
599 activeHIPTailEnd(shouldCloseTail && !(activeHIPTailEnd >= 0 && (activeHIPTailEnd - hipTb) < 1)) = hipTb;
600
601 // Tails are closed at the rising edge of the new trigger, which can lie before the saturated plateau of the old tail.
602 // The new tail inherits the plateau if it now contains saturated samples, the old one keeps it only if it still does.
603 auto newSatStart = satStart;
604 auto newSatEnd = satEnd;
605 const auto inheritSat = shouldCloseTail && satStart < 0 && activeHIPTailSatStart > -1 && (activeHIPTailSatEnd - activeHIPTailEnd) >= 0;
606 newSatStart(inheritSat) = activeHIPTailSatStart;
607 newSatEnd(inheritSat) = activeHIPTailSatEnd;
608 const auto oldLosesSat = shouldCloseTail && activeHIPTailSatStart > -1 && (activeHIPTailSatStart - activeHIPTailEnd) >= 0;
609 activeHIPTailSatStart(oldLosesSat) = -1;
610 activeHIPTailSatEnd(oldLosesSat) = -1;
611
612 // Closing tails will store them to global memory and zero the range
613 // So it's enough to disable this part to fully disable the tail filter
614 if (hipFilterOn && shouldCloseTail.isNotEmpty()) {
615 for (int16_t p = 0; p < PadsPerCacheline; p++) {
616 const int16_t pad = iVecPad * PadsPerCacheline + p;
617 if (shouldCloseTail[p] && pad < nPads) {
618 Charge tailQtot = 0;
619 Charge tailQMax = 0;
620 for (int16_t tt = activeHIPTailStart[p]; tt < activeHIPTailEnd[p]; tt++) {
621 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0);
622 const auto pos = basePos.delta({p, tt});
623 const auto q = chargeMap[pos].unpack();
624 tailQtot += q;
625 tailQMax = CAMath::Max(tailQMax, q);
626 chargeMap[pos] = PackedCharge{0};
627 }
628
629 if (activeHIPTailEnd[p] > activeHIPTailStart[p]) { // Prune empty tails
630 const auto tailIdx = CAMath::AtomicAdd<uint32_t>(&nHIPTails[row], 1) + 1;
632 hipTails[tailIdx] = {
633 .iPrev = 0,
634 .iNext = 0,
635 .pad = uint16_t(pad),
636 .tailStart = uint16_t(activeHIPTailStart[p]),
637 .tailEnd = uint16_t(activeHIPTailEnd[p]),
638 .satStart = int16_t(activeHIPTailSatStart[p]),
639 .satEnd = int16_t(activeHIPTailSatEnd[p]),
640 .qTot = tailQtot,
641 .qMax = tailQMax,
642 };
643 }
644 }
645
646 } // if (shouldCloseTail[p] && pad < nPads)
647 } // for (uint16_t p = 0; p < PadsPerCacheline; p++)
648 } // if (shouldCloseThipFilterOn && shouldCloseTail.isNotEmpty())
649
650 activeHIPTailStart(hipTb > -1) = hipTb;
651 activeHIPTailEnd(hipTb > -1) = -1;
652 activeHIPTailSatStart(hipTb > -1) = newSatStart;
653 activeHIPTailSatEnd(hipTb > -1) = newSatEnd;
654 tailFilterCharge(hipTb > -1) = MaxADC;
655
656 aboveThresholdStartV[iVecPad] = aboveThresholdStart;
657 activeHIPTailStartV[iVecPad] = activeHIPTailStart;
658 activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
659 activeHIPTailSatStartV[iVecPad] = activeHIPTailSatStart;
660 activeHIPTailSatEndV[iVecPad] = activeHIPTailSatEnd;
661 tailFilterChargeV[iVecPad] = tailFilterCharge;
662
663 } // for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
664 } // for (auto t = 0; t < frag.length; t += TimebinsPerCacheline)
665
666 // Close old tails for all pads, open new tails in case of overlap
667 for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) {
668
669 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
670 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
671 const auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
672 const auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
673
674 const auto shouldCloseTail = activeHIPTailStart > -1;
675 activeHIPTailEnd(shouldCloseTail && activeHIPTailEnd < 0) = frag.length;
676
677 if (hipFilterOn && shouldCloseTail.isNotEmpty()) {
678 for (int16_t p = 0; p < PadsPerCacheline; p++) {
679 const int16_t pad = iVecPad * PadsPerCacheline + p;
680 if (shouldCloseTail[p] && pad < nPads) {
681 Charge tailQtot = 0;
682 Charge tailQMax = 0;
683 for (int16_t tt = activeHIPTailStart[p]; tt < activeHIPTailEnd[p]; tt++) {
684 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0);
685 const auto pos = basePos.delta({p, tt});
686 const auto q = chargeMap[pos].unpack();
687 tailQtot += q;
688 tailQMax = CAMath::Max(tailQMax, q);
689 chargeMap[pos] = PackedCharge{0};
690 }
691
692 if (activeHIPTailEnd[p] > activeHIPTailStart[p]) { // Prune empty tails
693 const auto tailIdx = CAMath::AtomicAdd<uint32_t>(&nHIPTails[row], 1) + 1;
695 hipTails[tailIdx] = {
696 .iPrev = 0,
697 .iNext = 0,
698 .pad = uint16_t(pad),
699 .tailStart = uint16_t(activeHIPTailStart[p]),
700 .tailEnd = uint16_t(activeHIPTailEnd[p]),
701 .satStart = int16_t(activeHIPTailSatStart[p]),
702 .satEnd = int16_t(activeHIPTailSatEnd[p]),
703 .qTot = tailQtot,
704 .qMax = tailQMax,
705 };
706 }
707 }
708
709 } // if (shouldCloseTail[p] && pad < nPads)
710 } // for (uint16_t p = 0; p < PadsPerCacheline; p++)
711 } // if (hipFilterOn && shouldCloseTail.isNotEmpty())
712 } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
713
714 for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) {
715
716 const UShort8 totalCharges = totalChargesV[iVecPad];
717 const UShort8 maxConsecCharges = maxConsecChargesV[iVecPad];
718 const Charge8 maxCharge = maxChargeV[iVecPad];
719
720 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0);
721
722 for (tpccf::Pad localpad = 0; localpad < PadsPerCacheline; localpad++) {
723 updatePadBaseline(basePos.gpad + localpad, clusterer, totalCharges[localpad], maxConsecCharges[localpad], maxCharge[localpad]);
724 }
725 }
726#endif
727}
728
729GPUd() void GPUTPCCFCheckPadBaseline::updatePadBaseline(int32_t pad, const GPUTPCClusterFinder& clusterer, int32_t totalCharges, int32_t consecCharges, Charge maxCharge)
730{
731 const CfFragment& frag = clusterer.mPmemory->frag;
732 const int32_t totalChargesBaseline = clusterer.Param().rec.tpc.maxTimeBinAboveThresholdIn1000Bin * frag.lengthWithoutOverlap() / 1000;
733 const int32_t consecChargesBaseline = clusterer.Param().rec.tpc.maxConsecTimeBinAboveThreshold;
734 const uint16_t saturationThreshold = clusterer.Param().rec.tpc.noisyPadSaturationThreshold;
735 const bool isNoisy = (!saturationThreshold || maxCharge < saturationThreshold) && ((totalChargesBaseline > 0 && totalCharges >= totalChargesBaseline) || (consecChargesBaseline > 0 && consecCharges >= consecChargesBaseline));
736
737 if (isNoisy) {
738 clusterer.mPpadIsNoisy[pad] = true;
739 }
740}
741
742// ======== HIP Tail Connector Kernel ========
743
744template <>
745GPUd() void GPUTPCCFHIPTailConnector::Thread<0>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
746{
747 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
748 return;
749 }
750 const uint32_t row = iBlock;
751
752 const uint32_t nTails = CAMath::Min(clusterer.mPnHIPTails[row], (uint32_t)MaxHIPTailsPerRow - 1);
753
754 // HIP TAILS: indexing starts at 1, so 0 index indicates no connection
755 HIPTailDescriptor* tails = GetHIPTails(clusterer, row);
756
757#ifdef GPUCA_DETERMINISTIC_MODE
758 // Races in tail comparisons and atomic swap can lead to slightly different clusters.
759 // So need a sequential fallback for deterministic mode
760 GPUCommonAlgorithm::sortInBlock(tails + 1, tails + nTails + 1, [](auto&& t1, auto&& t2) {
761 if (t1.pad != t2.pad) {
762 return t1.pad < t2.pad;
763 } else if (t1.tailStart != t2.tailStart) {
764 return t1.tailStart < t2.tailStart;
765 } else if (t1.tailEnd != t2.tailEnd) {
766 return t1.tailEnd < t2.tailEnd;
767 } else if (t1.satStart != t2.satStart) {
768 return t1.satStart < t2.satStart;
769 } else if (t1.satEnd != t2.satEnd) {
770 return t1.satEnd < t2.satEnd;
771 } else if (t1.qTot != t2.qTot) {
772 return t1.qTot < t2.qTot;
773 } else {
774 return t1.qMax < t2.qMax;
775 }
777 if (iThread > 0) {
778 return;
779 }
780 nThreads = 1;
781#endif
782
783 for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads) {
784 auto* tail = &tails[iTail];
785
786 // TODO: this is needed because tailStarts may vary due to rising edge
787 // Better approach would be to also track the triggered timebin and match that instead
788 uint16_t overlapWindowStart = tail->tailStart >= 5 ? tail->tailStart - 5 : 0;
789 uint16_t overlapWindowEnd = tail->tailStart + 5;
790
791 for (uint32_t jTail = iTail + 1; jTail <= nTails; jTail++) {
792 auto* tailNext = &tails[jTail];
793 if (tailNext->iPrev > 0) {
794 continue;
795 }
796
797 const bool overlapPad = tailNext->pad >= tail->pad - GPUTPCCFCheckPadBaseline::SSClusterPadWidth && tailNext->pad <= tail->pad + GPUTPCCFCheckPadBaseline::SSClusterPadWidth;
798 const bool overlapTime = tailNext->tailStart >= overlapWindowStart && tailNext->tailStart < overlapWindowEnd;
799
800 if (overlapPad && overlapTime) {
801 if (CAMath::AtomicCAS(&tailNext->iPrev, 0u, iTail)) {
802 tail->iNext = jTail;
803 break;
804 }
805 }
806 }
807 }
808}
809
810// ======== HIP Clusterizer Kernel ========
811
812template <>
813GPUd() void GPUTPCCFHIPClusterizer::Thread<0>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer, uint8_t onlyMC)
814{
815 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
816 return;
817 }
818
819 const uint32_t row = iBlock;
820 uint32_t nTails = clusterer.mPnHIPTails[row];
821 nTails = CAMath::Min(nTails, (uint32_t)MaxHIPTailsPerRow - 1);
822
823 const auto* tails = GetHIPTails(clusterer, row);
824 const auto& frag = clusterer.mPmemory->frag;
825
826 auto* clusterPosInRow = clusterer.mPhipClusterPosInRow
827 ? clusterer.mPhipClusterPosInRow + row * MaxHIPTailsPerRow
828 : nullptr;
829
830 for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads) {
831
832 const auto* tail = &tails[iTail];
833 if (tail->iPrev != 0) {
834 continue;
835 }
836
837 CPU_ONLY(auto labelAcc = MCLabelAccumulator{clusterer});
838
839 float qTot = 0;
840 float qMax = 0;
841 float padSum = 0;
842 float padSqSum = 0;
843 float satTimeSum = 0;
844 uint32_t nSatTails = 0;
845 uint32_t tailStart = (uint32_t)-1;
846 uint32_t tailEnd = 0;
847
848 // Zero-th element is empty tail
849 for (; tail != tails; tail = &tails[tail->iNext]) {
850 const float tailWeight = tail->qTot;
851 const float tailPad = tail->pad;
852 qMax = CAMath::Max(qMax, tail->qMax);
853 qTot += tail->qTot;
854 padSum += tailWeight * tailPad;
855 padSqSum += tailWeight * tailPad * tailPad;
856 if (tail->satStart >= 0 && tail->satEnd >= 0) {
857 satTimeSum += 0.5f * (tail->satStart + tail->satEnd);
858 nSatTails++;
859 }
860 tailStart = CAMath::Min<uint32_t>(tailStart, tail->tailStart);
861 tailEnd = CAMath::Max<uint32_t>(tailEnd, tail->tailEnd);
862
863 CPU_ONLY(labelAcc.collectTail(row, tail->pad, tail->tailStart, tail->tailEnd));
864 }
865
866 const float weightSum = CAMath::Max(qTot, 1.f);
867 const float padMean = padSum / weightSum;
868 const float padSigma = CAMath::Sqrt(CAMath::Max(0.f, padSqSum / weightSum - padMean * padMean));
869
871 cn.qMax = qMax;
872 cn.setSaturatedQtot(qTot);
873 cn.setSaturatedTailLength(tailEnd - tailStart);
874 cn.setPad(padMean);
875 cn.setSigmaPad(padSigma);
876
877 if (cn.qMax >= 1023) {
878
879 // Use the middle of the saturated plateau, averaged over all tails of the cluster that were triggered by saturation on their own pad.
880 // Computed only here: chains consisting only of tails inherited from neighboring pads have no saturated plateau,
881 // but these never contain a saturated charge and are dropped by the qMax cut.
882 const float clusterTime = frag.start + satTimeSum / nSatTails - clusterer.Param().rec.tpc.clustersShiftTimebinsClusterizer;
883 assert(!CAMath::IsNaN(clusterTime));
884 cn.setTimeFlags(clusterTime, 0);
885
886 uint32_t index;
887
888 if (!onlyMC) {
889 // Cut off clusters where the tail connection failed for some reason
890 // TODO: Deduplicate with GPUTPCCFClusterizer::sortIntoBuckets (can't call cross-kernel).
891 // TODO: Add error reporting for row cluster overflow.
892 index = CAMath::AtomicAdd(&clusterer.mPclusterInRow[row], 1u);
893 if (index < clusterer.mNMaxClusterPerRow) {
894 clusterer.mPclusterByRow[clusterer.mNMaxClusterPerRow * row + index] = cn;
895 }
896 if (clusterPosInRow) {
897 clusterPosInRow[iTail] = index;
898 }
899 } else {
900 index = clusterPosInRow[iTail];
901 }
902
903 CPU_ONLY(labelAcc.commit(row, index, clusterer.mNMaxClusterPerRow));
904 }
905
906 } // for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads)
907}
Class of a TPC cluster in TPC-native coordinates (row, time)
int16_t time
Definition RawEventData.h:4
int32_t i
#define GPUCA_THREAD_INFO_PROVIDE
#define GPUbarrier()
#define GPUCA_GET_THREAD_COUNT(...)
#define DPRINT(...)
#define DPRINTB(...)
#define DPRINTB_IF(test,...)
GPUd() void GPUTPCCFCheckPadBaseline
static Charge charge
static int32_t row
uint16_t pos
Definition RawData.h:3
Provides a basic fallback implementation for Vc.
#define CPU_ONLY(x)
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLuint index
Definition glcorearb.h:781
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLint GLenum GLboolean GLsizei stride
Definition glcorearb.h:867
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
Definition glcorearb.h:5034
GPUdi() o2
Definition TrackTRD.h:39
tpccf::TPCFragmentTime length
Definition CfFragment.h:33
tpccf::TPCTime start
Definition CfFragment.h:31