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 HIPTailTimeMean(const HIPTailDescriptor& tail)
59{
60 const float length = tail.tailEnd > tail.tailStart ? float(tail.tailEnd - tail.tailStart) : 1.f;
61 return tail.tailStart + 0.5f * (length - 1.f);
62}
63
64static GPUdi() float HIPTailTimeVariance(const HIPTailDescriptor& tail)
65{
66 const float length = tail.tailEnd > tail.tailStart ? float(tail.tailEnd - tail.tailStart) : 1.f;
67 return (length * length - 1.f) * (1.f / 12.f);
68}
69
70// Collect tails marked for closing across the workgroup using a prefix scan,
71// then cooperatively zero the charge map entries for each closed tail.
72// Caller must set acc.activeHIPTail.end before calling if the tail is open.
73static GPUdi() uint16_t CloseHIPTails(
74 Kernel::GPUSharedMemory& smem,
75 GPUTPCClusterFinder& clusterer,
76 int32_t iThread, int32_t nThreads,
77 int16_t iPadHandle,
78 CfChargePos basePos,
79 CfArray2D<PackedCharge>& chargeMap,
80 Kernel::PadChargeAccu& acc,
81 bool shouldCloseTail)
82{
83 const uint32_t row = basePos.row();
84 const uint16_t nClosedTails = work_group_count(shouldCloseTail);
85
86 auto* nHIPTails = clusterer.mPnHIPTails;
87 auto* hipTails = GetHIPTails(clusterer, row);
88
89 if (nClosedTails > 0) {
90 int16_t iClosedTail = work_group_scan_inclusive_add((int16_t)shouldCloseTail) - 1;
91 const bool shouldStoreTail = shouldCloseTail && acc.activeHIPTail.Length() > 0;
92 uint16_t nStoredTails = work_group_count(shouldStoreTail);
93 int16_t iStoredTail = work_group_scan_inclusive_add((int16_t)shouldStoreTail) - 1;
94
95 // Use exactly one atomic add per closing call to reduce differences in
96 // tail ordering between runs.
97 if (nStoredTails > 0) {
98 if (iThread == 0) {
99 smem.tailStoreBase = CAMath::AtomicAdd(&nHIPTails[row], (uint32_t)nStoredTails);
100 }
101 GPUbarrier();
102 }
103 if (shouldCloseTail) {
104 smem.tailsClosedPad[iClosedTail] = iPadHandle;
105 smem.tailsClosed[iClosedTail] = acc.activeHIPTail;
106 smem.tailsClosedStoreIdx[iClosedTail] = GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow;
107
108 if (shouldStoreTail) {
109 const uint32_t idx = smem.tailStoreBase + iStoredTail + 1;
110 smem.tailsClosedStoreIdx[iClosedTail] = idx;
112 hipTails[idx] = {0, 0, (uint16_t)iPadHandle,
113 (uint16_t)acc.activeHIPTail.start, (uint16_t)acc.activeHIPTail.end,
114 0.f, 0.f};
115 }
116 }
117
118 acc.tailFilterCharge = 0;
119 acc.activeHIPTail.Reset();
120 }
121
122 GPUbarrier();
123 }
124
125 // TODO: performance improvement -> parallelize this loop across tails
126 for (uint16_t iTail = 0; iTail < nClosedTails; iTail++) {
127 const auto tailPad = smem.tailsClosedPad[iTail];
128 const auto tail = smem.tailsClosed[iTail];
129 const uint32_t tailStoreIdx = smem.tailsClosedStoreIdx[iTail];
130
131 Charge qTot = 0.f;
132 Charge qMax = 0.f;
133 for (uint16_t iTime = iThread; iTime < tail.Length(); iTime += nThreads) {
134 const int16_t time = tail.start + iTime;
135 auto pos = basePos.delta({tailPad, time});
136 const Charge q = chargeMap[pos].unpack();
137 qTot += q;
138 qMax = CAMath::Max(qMax, q);
139 chargeMap[pos] = PackedCharge{0};
140 }
141
142 smem.tailQTotScratch[iThread] = qTot;
143 smem.tailQMaxScratch[iThread] = qMax;
144 GPUbarrier();
145 for (uint16_t active = nThreads; active > 1;) {
146 const uint16_t stride = (active + 1) / 2;
147 if (iThread < active - stride) {
148 smem.tailQTotScratch[iThread] += smem.tailQTotScratch[iThread + stride];
149 smem.tailQMaxScratch[iThread] = CAMath::Max(smem.tailQMaxScratch[iThread], smem.tailQMaxScratch[iThread + stride]);
150 }
151 active = stride;
152 GPUbarrier();
153 }
154
155 if (iThread == 0 && tailStoreIdx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) {
156 HIPTailDescriptor& tailDescriptor = hipTails[tailStoreIdx];
157 tailDescriptor.qTot = smem.tailQTotScratch[0];
158 tailDescriptor.qMax = smem.tailQMaxScratch[0];
159 }
160 }
161
162 return nClosedTails;
163}
164
165template <bool CheckHIPTrigger, bool CheckHIPTailEnd>
166static GPUdi() void ScanCachedCharges(Kernel::GPUSharedMemory& smem, uint16_t timeOffset, uint16_t pad, Charge hipTailThreshold, Charge hipTailFilterAlpha, Kernel::PadChargeAccu& acc)
167{
168 for (int32_t i = 0; i < Kernel::NumOfCachedTBs; i++) {
169 const Charge qs = smem.charges[i][pad];
170 const int16_t curTB = timeOffset + i;
171
172 acc.totalCharges += qs > 0;
173 acc.consecCharges = qs > 0 ? acc.consecCharges + 1 : 0;
174 acc.maxConsecCharges = CAMath::Max(acc.consecCharges, acc.maxConsecCharges);
175 acc.maxCharge = CAMath::Max<Charge>(qs, acc.maxCharge);
176
177 if (qs >= hipTailThreshold) {
178 if (acc.aboveThresholdStart < 0) {
179 acc.aboveThresholdStart = curTB;
180 }
181 } else {
182 acc.aboveThresholdStart = -1;
183 }
184
185 if constexpr (CheckHIPTrigger) {
186 if (acc.HIPtb < 0 && qs >= Charge(Kernel::MaxADC)) {
187 acc.HIPtb = acc.aboveThresholdStart; // start of rising edge, not first sat TB
188 smem.tails[pad] = {acc.HIPtb, 0}; // Broadcast HIP start TB to neighboring pads / threads
189 }
190 }
191
192 if constexpr (CheckHIPTailEnd) {
193 if (acc.activeHIPTail.IsOpen()) {
194 acc.tailFilterCharge = UpdateHIPTailFilter(acc.tailFilterCharge, qs, hipTailFilterAlpha);
195 if (acc.tailFilterCharge < hipTailThreshold) {
196 acc.activeHIPTail.end = curTB;
197 }
198 }
199 }
200 }
201}
202
203template <>
204GPUd() void GPUTPCCFCheckPadBaseline::Thread<0>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
205{
206#ifdef GPUCA_GPUCODE
207 CheckBaselineGPU(nBlocks, nThreads, iBlock, iThread, smem, clusterer);
208#else
209 CheckBaselineCPU(nBlocks, nThreads, iBlock, iThread, smem, clusterer);
210#endif
211}
212
213// Charges are stored in a 2D array (pad and time) using a tiling layout.
214// Tiles are 8 pads x 4 timebins large stored in time-major layout and make up a single cacheline.
215//
216// This kernel processes one row per block. Threads cooperatively load chunks
217// of 4 consecutive time bins for all pads into shared memory. Thread `i` then processes charges for pad `i` in shared memory.
218// Blocks require `nextMultipleOf<64>(138 * 4) = 576` threads to process the largest TPC rows with 138 pads correctly.
219GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
220{
221#ifdef GPUCA_GPUCODE
222 static_assert(GPUCA_GET_THREAD_COUNT(GPUCA_LB_GPUTPCCFCheckPadBaseline) == 576);
223 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
224 return;
225 }
226
227 const CfFragment& fragment = clusterer.mPmemory->fragment;
228 const bool hipFilterOn = clusterer.Param().rec.tpc.hipTailFilter;
229 const Charge hipTailThreshold = clusterer.Param().rec.tpc.hipTailFilterThreshold;
230 const Charge hipTailFilterAlpha = clusterer.Param().rec.tpc.hipTailFilterAlpha;
231 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
232
233 constexpr GPUTPCGeometry geo;
234
235 const auto iRow = iBlock;
236 const auto nPads = geo.NPads(iRow);
237 const CfChargePos basePos{(Row)iRow, 0, 0};
238
239 PadChargeAccu acc;
240
241 const int16_t iPadOffset = iThread % MaxNPadsPerRow;
242 const int16_t iTimeOffset = iThread / MaxNPadsPerRow;
243 const int16_t iPadHandle = iThread;
244 const bool handlePad = iPadHandle < nPads;
245
246 if (iPadHandle < MaxNPadsPerRow) {
247 smem.tails[iPadHandle] = {-1, -1};
248 }
249 GPUbarrier();
250
251 // Pad filter scans the entire fragments including overlap.
252 // Minimal runtime overhead and prevents headaches later on as
253 // saturated signal in overlap region can create tails in the next fragment
254 // even when cleared in current fragment as they're decoded twice
255 const TPCFragmentTime firstTB = 0;
256 const TPCFragmentTime lastTB = fragment.length;
257
258 for (uint16_t t = firstTB; t < lastTB; t += NumOfCachedTBs) {
259
260 bool thisThreadHasTrigger = false;
261 for (uint16_t tt = 0; tt < NumOfCachedTBs; tt += TimebinsPerCacheline) {
262 const TPCFragmentTime iTimeLoad = t + tt + iTimeOffset;
263
264 const CfChargePos pos = basePos.delta({iPadOffset, iTimeLoad});
265
266 const Charge ql = iTimeLoad < lastTB && iPadOffset < nPads ? chargeMap[pos].unpack() : 0;
267 smem.charges[tt + iTimeOffset][iPadOffset] = ql;
268
269 thisThreadHasTrigger |= ql >= Charge(MaxADC);
270 }
271
272 bool hasHIPTrigger = false;
273 if (hipFilterOn) {
274 hasHIPTrigger = work_group_any(thisThreadHasTrigger);
275 } else {
276 // Need a barrier here even if HIP filter is disabled
277 GPUbarrier();
278 }
279
280 acc.HIPtb = -1;
281
282 if (handlePad) {
283
284 // TODO: is this really necessary?
285 // Why is the old version so much slower, when we just add short branches to the loop???
286 if (!hasHIPTrigger) [[likely]] {
287 if (!acc.activeHIPTail.IsOpen()) {
288 ScanCachedCharges<false, false>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
289 } else {
290 ScanCachedCharges<false, true>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
291 }
292 } else {
293 if (!acc.activeHIPTail.IsOpen()) {
294 ScanCachedCharges<true, false>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
295 } else {
296 ScanCachedCharges<true, true>(smem, t, iPadHandle, hipTailThreshold, hipTailFilterAlpha, acc);
297 }
298 }
299 }
300
301 GPUbarrier();
302
303 if (hasHIPTrigger) [[unlikely]] {
304
305 DPRINTB("%d: Trigger!\n", iBlock);
306
307 if (handlePad && acc.HIPtb < 0) {
308
309 // Search neighboring pads for trigger
310 for (int16_t i = -SSClusterPadWidth; i < 0; i++) {
311 const auto p = iPadHandle + i;
312 if (p > -1) {
313 acc.HIPtb = CAMath::Max(smem.tails[p].start, acc.HIPtb);
314 }
315 }
316
317 for (int16_t i = 1; i <= SSClusterPadWidth; i++) {
318 const auto p = iPadHandle + i;
319 if (p < MaxNPadsPerRow) {
320 acc.HIPtb = CAMath::Max(smem.tails[p].start, acc.HIPtb);
321 }
322 }
323 }
324
325 bool shouldCloseTail = acc.HIPtb > -1 && acc.activeHIPTail.HasValue();
326 if (shouldCloseTail && acc.activeHIPTail.IsOpen()) {
327 DPRINT("%d: end = %d\n", iThread, acc.HIPtb);
328 acc.activeHIPTail.end = acc.HIPtb;
329 }
330
331 CloseHIPTails(smem, clusterer, iThread, nThreads, iPadHandle, basePos, chargeMap, acc, shouldCloseTail);
332
333 GPUbarrier();
334
335 if (acc.HIPtb > -1) {
336 DPRINT("%d: start = %d\n", iThread, acc.HIPtb);
337 acc.activeHIPTail.SetOpen(acc.HIPtb);
338 acc.tailFilterCharge = Charge(MaxADC);
339 }
340
341 // Clear smem between iterations to prevent stale entries
342 if (handlePad) {
343 smem.tails[iPadHandle].Reset();
344 }
345
346 GPUbarrier();
347
348 } // if (hasHIPTrigger)
349
350 } // for (uint16_t t = firstTB; t < lastTB; t += NumOfCachedTBs)
351
352 if (handlePad) {
353 updatePadBaseline(basePos.gpad + iPadHandle, clusterer, acc.totalCharges, acc.maxConsecCharges, acc.maxCharge);
354 }
355
356 // --- Close remaining tails
357 const bool shouldCloseTail = acc.activeHIPTail.HasValue();
358
359 // Call `work_group_any` here, instead of always counting.
360 // This is important as `work_group_count` is a lot slower
361 // and has a lot of overhead if no HIPs were found.
362 if (work_group_any(shouldCloseTail)) {
363 if (shouldCloseTail && acc.activeHIPTail.IsOpen()) {
364 acc.activeHIPTail.end = lastTB;
365 }
366
367 [[maybe_unused]] const uint16_t nClosedTails = CloseHIPTails(smem, clusterer, iThread, nThreads, iPadHandle, basePos, chargeMap, acc, shouldCloseTail);
368
369 DPRINTB_IF(nClosedTails > 0, "%d: Close remaining tails (%d)\n", iBlock, nClosedTails);
370 }
371
372#endif
373}
374
375GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
376{
377#ifndef GPUCA_GPUCODE
378 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
379 return;
380 }
381
382 constexpr GPUTPCGeometry geo;
383 const int32_t row = iBlock;
384 const int32_t nPads = geo.NPads(row);
385 const int32_t nVecPads = (nPads + PadsPerCacheline - 1) / PadsPerCacheline;
386
387 const CfFragment& fragment = clusterer.mPmemory->fragment;
388 const bool hipFilterOn = clusterer.Param().rec.tpc.hipTailFilter;
389 const Charge hipTailThreshold = clusterer.Param().rec.tpc.hipTailFilterThreshold;
390 const Charge hipTailFilterAlpha = clusterer.Param().rec.tpc.hipTailFilterAlpha;
391 auto* nHIPTails = clusterer.mPnHIPTails;
392 auto* hipTails = GetHIPTails(clusterer, row);
393
394 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
395
396 using UShort8 = Vc::fixed_size_simd<uint16_t, PadsPerCacheline>;
397 using Short8 = Vc::fixed_size_simd<int16_t, PadsPerCacheline>;
398 using Charge8 = Vc::fixed_size_simd<float, PadsPerCacheline>;
399
400 std::vector<UShort8> totalChargesV(nVecPads, UShort8{Vc::Zero});
401 std::vector<UShort8> consecChargesV(nVecPads, UShort8{Vc::Zero});
402 std::vector<UShort8> maxConsecChargesV(nVecPads, UShort8{Vc::Zero});
403 std::vector<Charge8> maxChargeV(nVecPads, Charge8{Vc::Zero});
404
405 std::vector<Short8> localHipTbV(nVecPads, -1);
406 std::vector<Short8> broadcastHipTbV(nVecPads, -1);
407 std::vector<Short8> aboveThresholdStartV(nVecPads, -1);
408 std::vector<Short8> activeHIPTailStartV(nVecPads, -1);
409 std::vector<Short8> activeHIPTailEndV(nVecPads, -1);
410 std::vector<Charge8> tailFilterChargeV(nVecPads, Charge8{Vc::Zero});
411
412 for (int16_t t = 0; t < fragment.length; t += NumOfCachedTBs) {
413
414 bool hasAnyTrigger = false;
415
416 // Run actual noisy pad filter and look for HIP trigger
417 for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) {
418
419 auto totalCharges = totalChargesV[iVecPad];
420 auto consecCharges = consecChargesV[iVecPad];
421 auto maxConsecCharges = maxConsecChargesV[iVecPad];
422 auto maxCharge = maxChargeV[iVecPad];
423
424 auto hipTb = Short8(-1);
425 auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
426 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
427 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
428 auto tailFilterCharge = tailFilterChargeV[iVecPad];
429
430 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, t);
431
432 for (tpccf::TPCFragmentTime localtime = 0; localtime < NumOfCachedTBs; localtime++) {
433
434 const uint16_t* packedChargeStart = reinterpret_cast<uint16_t*>(&chargeMap[basePos.delta({0, localtime})]);
435 const UShort8 packedCharges = t + localtime < fragment.length
436 ? UShort8{packedChargeStart, Vc::Aligned}
437 : UShort8{Vc::Zero};
438 const auto isCharge = packedCharges != 0;
439
440 const auto unpackedCharges = Charge8(packedCharges) / Charge(1 << PackedCharge::DecimalBits);
441
442 if (isCharge.isNotEmpty()) {
443 totalCharges(isCharge)++;
444 consecCharges += 1;
445 consecCharges(not isCharge) = 0;
446 maxConsecCharges = Vc::max(consecCharges, maxConsecCharges);
447
448 // Manually unpack charges to float.
449 // Duplicated from PackedCharge::unpack to generate vectorized code:
450 // Charge unpack() const { return Charge(mVal & ChargeMask) / Charge(1 << DecimalBits); }
451 // Note that PackedCharge has to cut off the highest 2 bits via ChargeMask as they are used for flags by the cluster finder
452 // and are not part of the charge value. We can skip this step because the cluster finder hasn't run yet
453 // and thus these bits are guarenteed to be zero.
454 maxCharge = Vc::max(maxCharge, unpackedCharges);
455
456 const auto aboveRisingEdge = unpackedCharges >= hipTailThreshold;
457 const auto startRisingEdge = aboveRisingEdge && aboveThresholdStart < 0;
458 aboveThresholdStart(startRisingEdge) = t + localtime;
459 aboveThresholdStart(!aboveRisingEdge) = -1;
460
461 const auto hasNewTrigger = hipTb < 0 && unpackedCharges >= Charge(MaxADC);
462 hipTb(hasNewTrigger) = aboveThresholdStart;
463 hasAnyTrigger |= hasNewTrigger.isNotEmpty();
464 } else {
465 consecCharges = 0;
466 aboveThresholdStart = -1;
467 }
468
469 const auto tailOpen = activeHIPTailStart > -1 && activeHIPTailEnd < 0;
470 tailFilterCharge(tailOpen) = tailFilterCharge + hipTailFilterAlpha * (unpackedCharges - tailFilterCharge);
471 activeHIPTailEnd(tailOpen && tailFilterCharge < hipTailThreshold) = t + localtime;
472 } // for (tpccf::TPCFragmentTime localtime = 0; localtime < TimebinsPerCacheline; localtime++)
473
474 totalChargesV[iVecPad] = totalCharges;
475 consecChargesV[iVecPad] = consecCharges;
476 maxConsecChargesV[iVecPad] = maxConsecCharges;
477 maxChargeV[iVecPad] = maxCharge;
478
479 localHipTbV[iVecPad] = hipTb;
480 aboveThresholdStartV[iVecPad] = aboveThresholdStart;
481 activeHIPTailStartV[iVecPad] = activeHIPTailStart;
482 activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
483 tailFilterChargeV[iVecPad] = tailFilterCharge;
484
485 } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
486
487 if (hasAnyTrigger) {
488 broadcastHipTbV = localHipTbV;
489 }
490
491 // Broadcast trigger times to neighboring pads across the whole
492 for (int16_t iVecPad = 0; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) {
493
494 const auto hipTb = localHipTbV[iVecPad];
495
496 const auto hasHipTrigger = hipTb > -1;
497 if (hasHipTrigger.isNotEmpty()) [[unlikely]] {
498
499 // TODO: This could be vectorised, but doesn't seem necessary
500 for (uint16_t p = 0; p < PadsPerCacheline; p++) {
501 if (hasHipTrigger[p]) {
502 const int16_t pad = iVecPad * PadsPerCacheline + p;
503 const int16_t neighborSt = CAMath::Max(0, pad - SSClusterPadWidth);
504 const int16_t neighborEnd = CAMath::Min(nPads, pad + SSClusterPadWidth + 1);
505 for (int16_t np = neighborSt; np < neighborEnd; np++) {
506 if (np == pad) {
507 continue;
508 }
509 const auto pv = np / PadsPerCacheline;
510 const auto pi = np % PadsPerCacheline;
511 // GPU keeps a pad's own trigger time; only pads without a local trigger inherit from neighbors.
512 if (localHipTbV[pv][pi] < 0) {
513 broadcastHipTbV[pv][pi] = CAMath::Max<int16_t>(hipTb[p], broadcastHipTbV[pv][pi]);
514 }
515 } // for (int16_t np = neighborSt; np < neighborEnd; np++)
516 } // if (hasHipTrigger[p]) {
517 } // for (uint16_t p = 0; p < PadsPerCacheline; p++)
518 } // if (hasHipTrigger.isNotEmpty())
519 } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
520
521 // Close old tails for all pads, open new tails in case of overlap
522 for (int16_t iVecPad = 0; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) {
523
524 auto hipTb = broadcastHipTbV[iVecPad];
525 auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
526 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
527 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
528 auto tailFilterCharge = tailFilterChargeV[iVecPad];
529
530 const auto shouldCloseTail = hipTb > -1 && activeHIPTailStart > -1;
531 activeHIPTailEnd(shouldCloseTail && activeHIPTailEnd < 0) = hipTb;
532
533 // Closing tails will store them to global memory and zero the range
534 // So it's enough to disable this part to fully disable the tail filter
535 if (hipFilterOn && shouldCloseTail.isNotEmpty()) {
536 for (int16_t p = 0; p < PadsPerCacheline; p++) {
537 const int16_t pad = iVecPad * PadsPerCacheline + p;
538 if (shouldCloseTail[p] && pad < nPads) {
539 Charge tailQtot = 0;
540 Charge tailQMax = 0;
541 for (int16_t tt = activeHIPTailStart[p]; tt < activeHIPTailEnd[p]; tt++) {
542 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0);
543 const auto pos = basePos.delta({p, tt});
544 const auto q = chargeMap[pos].unpack();
545 tailQtot += q;
546 tailQMax = CAMath::Max(tailQMax, q);
547 chargeMap[pos] = PackedCharge{0};
548 }
549
550 if (activeHIPTailEnd[p] > activeHIPTailStart[p]) { // Prune empty tails
551 const auto tailIdx = CAMath::AtomicAdd<uint32_t>(&nHIPTails[row], 1) + 1;
553 hipTails[tailIdx] = {
554 .iPrev = 0,
555 .iNext = 0,
556 .pad = uint16_t(pad),
557 .tailStart = uint16_t(activeHIPTailStart[p]),
558 .tailEnd = uint16_t(activeHIPTailEnd[p]),
559 .qTot = tailQtot,
560 .qMax = tailQMax,
561 };
562 }
563 }
564
565 } // if (shouldCloseTail[p] && pad < nPads)
566 } // for (uint16_t p = 0; p < PadsPerCacheline; p++)
567 } // if (shouldCloseThipFilterOn && shouldCloseTail.isNotEmpty())
568
569 activeHIPTailStart(hipTb > -1) = hipTb;
570 activeHIPTailEnd(hipTb > -1) = -1;
571 tailFilterCharge(hipTb > -1) = MaxADC;
572
573 aboveThresholdStartV[iVecPad] = aboveThresholdStart;
574 activeHIPTailStartV[iVecPad] = activeHIPTailStart;
575 activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
576 tailFilterChargeV[iVecPad] = tailFilterCharge;
577
578 } // for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
579 } // for (auto t = 0; t < fragment.length; t += TimebinsPerCacheline)
580
581 // Close old tails for all pads, open new tails in case of overlap
582 for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) {
583
584 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
585 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
586
587 const auto shouldCloseTail = activeHIPTailStart > -1;
588 activeHIPTailEnd(shouldCloseTail && activeHIPTailEnd < 0) = fragment.length;
589
590 if (hipFilterOn && shouldCloseTail.isNotEmpty()) {
591 for (int16_t p = 0; p < PadsPerCacheline; p++) {
592 const int16_t pad = iVecPad * PadsPerCacheline + p;
593 if (shouldCloseTail[p] && pad < nPads) {
594 Charge tailQtot = 0;
595 Charge tailQMax = 0;
596 for (int16_t tt = activeHIPTailStart[p]; tt < activeHIPTailEnd[p]; tt++) {
597 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0);
598 const auto pos = basePos.delta({p, tt});
599 const auto q = chargeMap[pos].unpack();
600 tailQtot += q;
601 tailQMax = CAMath::Max(tailQMax, q);
602 chargeMap[pos] = PackedCharge{0};
603 }
604
605 if (activeHIPTailEnd[p] > activeHIPTailStart[p]) { // Prune empty tails
606 const auto tailIdx = CAMath::AtomicAdd<uint32_t>(&nHIPTails[row], 1) + 1;
608 hipTails[tailIdx] = {
609 .iPrev = 0,
610 .iNext = 0,
611 .pad = uint16_t(pad),
612 .tailStart = uint16_t(activeHIPTailStart[p]),
613 .tailEnd = uint16_t(activeHIPTailEnd[p]),
614 .qTot = tailQtot,
615 .qMax = tailQMax,
616 };
617 }
618 }
619
620 } // if (shouldCloseTail[p] && pad < nPads)
621 } // for (uint16_t p = 0; p < PadsPerCacheline; p++)
622 } // if (hipFilterOn && shouldCloseTail.isNotEmpty())
623 } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
624
625 for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) {
626
627 const UShort8 totalCharges = totalChargesV[iVecPad];
628 const UShort8 maxConsecCharges = maxConsecChargesV[iVecPad];
629 const Charge8 maxCharge = maxChargeV[iVecPad];
630
631 const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0);
632
633 for (tpccf::Pad localpad = 0; localpad < PadsPerCacheline; localpad++) {
634 updatePadBaseline(basePos.gpad + localpad, clusterer, totalCharges[localpad], maxConsecCharges[localpad], maxCharge[localpad]);
635 }
636 }
637#endif
638}
639
640GPUd() void GPUTPCCFCheckPadBaseline::updatePadBaseline(int32_t pad, const GPUTPCClusterFinder& clusterer, int32_t totalCharges, int32_t consecCharges, Charge maxCharge)
641{
642 const CfFragment& fragment = clusterer.mPmemory->fragment;
643 const int32_t totalChargesBaseline = clusterer.Param().rec.tpc.maxTimeBinAboveThresholdIn1000Bin * fragment.lengthWithoutOverlap() / 1000;
644 const int32_t consecChargesBaseline = clusterer.Param().rec.tpc.maxConsecTimeBinAboveThreshold;
645 const uint16_t saturationThreshold = clusterer.Param().rec.tpc.noisyPadSaturationThreshold;
646 const bool isNoisy = (!saturationThreshold || maxCharge < saturationThreshold) && ((totalChargesBaseline > 0 && totalCharges >= totalChargesBaseline) || (consecChargesBaseline > 0 && consecCharges >= consecChargesBaseline));
647
648 if (isNoisy) {
649 clusterer.mPpadIsNoisy[pad] = true;
650 }
651}
652
653// ======== HIP Tail Connector Kernel ========
654
655template <>
656GPUd() void GPUTPCCFHIPTailConnector::Thread<0>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer)
657{
658 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
659 return;
660 }
661 const uint32_t row = iBlock;
662
663 const uint32_t nTails = CAMath::Min(clusterer.mPnHIPTails[row], (uint32_t)MaxHIPTailsPerRow - 1);
664
665 // HIP TAILS: indexing starts at 1, so 0 index indicates no connection
666 HIPTailDescriptor* tails = GetHIPTails(clusterer, row);
667
668#ifdef GPUCA_DETERMINISTIC_MODE
669 // Races in tail comparisons and atomic swap can lead to slightly different clusters.
670 // So need a sequential fallback for deterministic mode
671 GPUCommonAlgorithm::sortInBlock(tails + 1, tails + nTails + 1, [](auto&& t1, auto&& t2) {
672 if (t1.pad != t2.pad) {
673 return t1.pad < t2.pad;
674 } else if (t1.tailStart != t2.tailStart) {
675 return t1.tailStart < t2.tailStart;
676 } else if (t1.tailEnd != t2.tailEnd) {
677 return t1.tailEnd < t2.tailEnd;
678 } else if (t1.qTot != t2.qTot) {
679 return t1.qTot < t2.qTot;
680 } else {
681 return t1.qMax < t2.qMax;
682 }
683 });
684 if (iThread > 0) {
685 return;
686 }
687 nThreads = 1;
688#endif
689
690 for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads) {
691 auto* tail = &tails[iTail];
692
693 // TODO: this is needed because tailStarts may vary due to rising edge
694 // Better approach would be to also track the triggered timebin and match that instead
695 uint16_t overlapWindowStart = tail->tailStart >= 5 ? tail->tailStart - 5 : 0;
696 uint16_t overlapWindowEnd = tail->tailStart + 5;
697
698 for (uint32_t jTail = iTail + 1; jTail <= nTails; jTail++) {
699 auto* tailNext = &tails[jTail];
700 if (tailNext->iPrev > 0) {
701 continue;
702 }
703
704 const bool overlapPad = tailNext->pad >= tail->pad - GPUTPCCFCheckPadBaseline::SSClusterPadWidth && tailNext->pad <= tail->pad + GPUTPCCFCheckPadBaseline::SSClusterPadWidth;
705 const bool overlapTime = tailNext->tailStart >= overlapWindowStart && tailNext->tailStart < overlapWindowEnd;
706
707 if (overlapPad && overlapTime) {
708 if (CAMath::AtomicCAS(&tailNext->iPrev, 0u, iTail)) {
709 tail->iNext = jTail;
710 break;
711 }
712 }
713 }
714 }
715}
716
717// ======== HIP Clusterizer Kernel ========
718
719template <>
720GPUd() void GPUTPCCFHIPClusterizer::Thread<0>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer, uint8_t onlyMC)
721{
722 if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) {
723 return;
724 }
725
726 const uint32_t row = iBlock;
727 uint32_t nTails = clusterer.mPnHIPTails[row];
728 nTails = CAMath::Min(nTails, (uint32_t)MaxHIPTailsPerRow - 1);
729
730 const auto* tails = GetHIPTails(clusterer, row);
731 const auto& fragment = clusterer.mPmemory->fragment;
732
733 auto* clusterPosInRow = clusterer.mPhipClusterPosInRow
734 ? clusterer.mPhipClusterPosInRow + row * MaxHIPTailsPerRow
735 : nullptr;
736
737 for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads) {
738
739 const auto* tail = &tails[iTail];
740 if (tail->iPrev != 0) {
741 continue;
742 }
743
744 CPU_ONLY(auto labelAcc = MCLabelAccumulator{clusterer});
745
746 float qTot = 0;
747 float qMax = 0;
748 float padSum = 0;
749 float padSqSum = 0;
750 float timeSum = 0;
751 uint32_t tailStart = (uint32_t)-1;
752 uint32_t tailEnd = 0;
753
754 // Zero-th element is empty tail
755 for (; tail != tails; tail = &tails[tail->iNext]) {
756 const float tailWeight = tail->qTot;
757 const float tailPad = tail->pad;
758 const float tailTime = HIPTailTimeMean(*tail);
759 qMax = CAMath::Max(qMax, tail->qMax);
760 qTot += tail->qTot;
761 padSum += tailWeight * tailPad;
762 padSqSum += tailWeight * tailPad * tailPad;
763 timeSum += tailWeight * tailTime;
764 tailStart = CAMath::Min<uint32_t>(tailStart, tail->tailStart);
765 tailEnd = CAMath::Max<uint32_t>(tailEnd, tail->tailEnd);
766
767 CPU_ONLY(labelAcc.collectTail(row, tail->pad, tail->tailStart, tail->tailEnd));
768 }
769
770 const float weightSum = CAMath::Max(qTot, 1.f);
771 const float padMean = padSum / weightSum;
772 const float timeMean = timeSum / weightSum; // TODO: Use timebin of saturated signal instead! Time mean is biased for long tails.
773 const float padSigma = CAMath::Sqrt(CAMath::Max(0.f, padSqSum / weightSum - padMean * padMean));
774
776 cn.qMax = qMax;
777 cn.setSaturatedQtot(qTot);
778 cn.setSaturatedTailLength(tailEnd - tailStart);
779 float clusterTime = fragment.start + timeMean - clusterer.Param().rec.tpc.clustersShiftTimebinsClusterizer;
780 cn.setTimeFlags(clusterTime, 0);
781 cn.setPad(padMean);
782 cn.setSigmaPad(padSigma);
783
784 if (cn.qMax >= 1023) {
785
786 uint32_t index;
787
788 if (!onlyMC) {
789 // Cut off clusters where the tail connection failed for some reason
790 // TODO: Deduplicate with GPUTPCCFClusterizer::sortIntoBuckets (can't call cross-kernel).
791 // TODO: Add error reporting for row cluster overflow.
792 index = CAMath::AtomicAdd(&clusterer.mPclusterInRow[row], 1u);
793 if (index < clusterer.mNMaxClusterPerRow) {
794 clusterer.mPclusterByRow[clusterer.mNMaxClusterPerRow * row + index] = cn;
795 }
796 if (clusterPosInRow) {
797 clusterPosInRow[iTail] = index;
798 }
799 } else {
800 index = clusterPosInRow[iTail];
801 }
802
803 CPU_ONLY(labelAcc.commit(row, index, clusterer.mNMaxClusterPerRow));
804 }
805
806 } // for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads)
807}
Class of a TPC cluster in TPC-native coordinates (row, time)
int16_t time
Definition RawEventData.h:4
int32_t i
#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.
static constexpr uint32_t NROWS
#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