Skip to content

Commit 992cd42

Browse files
fweigsawenzel
authored andcommitted
GPU: Fix time position of saturated clusters
Time position of saturated clusters is now fixed to the middle of the saturated plateau instead of the weighted average of the entire tail, which would bias the position towards the tail. Also fixes some rare issues with overlapping tails.
1 parent 0f0e233 commit 992cd42

2 files changed

Lines changed: 124 additions & 16 deletions

File tree

‎GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.cxx‎

Lines changed: 116 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,6 @@ static GPUdi() Charge UpdateHIPTailFilter(Charge filteredCharge, Charge charge,
5555
return filteredCharge + alpha * (charge - filteredCharge);
5656
}
5757

58-
static 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-
6458
static GPUdi() float HIPTailTimeVariance(const HIPTailDescriptor& tail)
6559
{
6660
const float length = tail.tailEnd > tail.tailStart ? float(tail.tailEnd - tail.tailStart) : 1.f;
@@ -111,12 +105,14 @@ static GPUdi() uint16_t CloseHIPTails(
111105
if (idx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) {
112106
hipTails[idx] = {0, 0, (uint16_t)iPadHandle,
113107
(uint16_t)acc.activeHIPTail.start, (uint16_t)acc.activeHIPTail.end,
108+
acc.activeSatStart, acc.activeSatEnd,
114109
0.f, 0.f};
115110
}
116111
}
117112

118113
acc.tailFilterCharge = 0;
119114
acc.activeHIPTail.Reset();
115+
acc.activeSatStart = acc.activeSatEnd = -1;
120116
}
121117

122118
GPUbarrier();
@@ -183,10 +179,26 @@ static GPUdi() void ScanCachedCharges(Kernel::GPUSharedMemory& smem, uint16_t ti
183179
}
184180

185181
if constexpr (CheckHIPTrigger) {
186-
if (acc.HIPtb < 0 && qs >= Charge(Kernel::MaxADC)) {
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) {
187195
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
188197
smem.tails[pad] = {acc.HIPtb, 0}; // Broadcast HIP start TB to neighboring pads / threads
189198
}
199+
if (isSaturated && acc.satStart > -1 && acc.satStart == acc.plateauStart) {
200+
acc.satEnd = curTB;
201+
}
190202
}
191203

192204
if constexpr (CheckHIPTailEnd) {
@@ -278,6 +290,10 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t
278290
}
279291

280292
acc.HIPtb = -1;
293+
acc.satStart = acc.satEnd = -1;
294+
if (!hasHIPTrigger) {
295+
acc.plateauOpen = false; // No saturated TB in this chunk
296+
}
281297

282298
if (handlePad) {
283299

@@ -323,11 +339,27 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t
323339
}
324340

325341
bool shouldCloseTail = acc.HIPtb > -1 && acc.activeHIPTail.HasValue();
326-
if (shouldCloseTail && acc.activeHIPTail.IsOpen()) {
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)) {
327345
DPRINT("%d: end = %d\n", iThread, acc.HIPtb);
328346
acc.activeHIPTail.end = acc.HIPtb;
329347
}
330348

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+
331363
CloseHIPTails(smem, clusterer, iThread, nThreads, iPadHandle, basePos, chargeMap, acc, shouldCloseTail);
332364

333365
GPUbarrier();
@@ -336,6 +368,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t
336368
DPRINT("%d: start = %d\n", iThread, acc.HIPtb);
337369
acc.activeHIPTail.SetOpen(acc.HIPtb);
338370
acc.tailFilterCharge = Charge(MaxADC);
371+
acc.activeSatStart = newSatStart;
372+
acc.activeSatEnd = newSatEnd;
339373
}
340374

341375
// Clear smem between iterations to prevent stale entries
@@ -404,9 +438,15 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
404438

405439
std::vector<Short8> localHipTbV(nVecPads, -1);
406440
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
407445
std::vector<Short8> aboveThresholdStartV(nVecPads, -1);
408446
std::vector<Short8> activeHIPTailStartV(nVecPads, -1);
409447
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
410450
std::vector<Charge8> tailFilterChargeV(nVecPads, Charge8{Vc::Zero});
411451

412452
for (int16_t t = 0; t < fragment.length; t += NumOfCachedTBs) {
@@ -422,6 +462,12 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
422462
auto maxCharge = maxChargeV[iVecPad];
423463

424464
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];
425471
auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
426472
auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
427473
auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
@@ -458,12 +504,23 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
458504
aboveThresholdStart(startRisingEdge) = t + localtime;
459505
aboveThresholdStart(!aboveRisingEdge) = -1;
460506

461-
const auto hasNewTrigger = hipTb < 0 && unpackedCharges >= Charge(MaxADC);
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;
462516
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;
463519
hasAnyTrigger |= hasNewTrigger.isNotEmpty();
464520
} else {
465521
consecCharges = 0;
466522
aboveThresholdStart = -1;
523+
plateauOpen = 0;
467524
}
468525

469526
const auto tailOpen = activeHIPTailStart > -1 && activeHIPTailEnd < 0;
@@ -477,6 +534,11 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
477534
maxChargeV[iVecPad] = maxCharge;
478535

479536
localHipTbV[iVecPad] = hipTb;
537+
localSatStartV[iVecPad] = satStart;
538+
localSatEndV[iVecPad] = satEnd;
539+
plateauStartV[iVecPad] = plateauStart;
540+
plateauOpenV[iVecPad] = plateauOpen;
541+
activeHIPTailSatEndV[iVecPad] = activeHIPTailSatEnd;
480542
aboveThresholdStartV[iVecPad] = aboveThresholdStart;
481543
activeHIPTailStartV[iVecPad] = activeHIPTailStart;
482544
activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
@@ -522,13 +584,30 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
522584
for (int16_t iVecPad = 0; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) {
523585

524586
auto hipTb = broadcastHipTbV[iVecPad];
587+
const auto satStart = localSatStartV[iVecPad];
588+
const auto satEnd = localSatEndV[iVecPad];
525589
auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
526590
auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
527591
auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
592+
auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
593+
auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
528594
auto tailFilterCharge = tailFilterChargeV[iVecPad];
529595

530596
const auto shouldCloseTail = hipTb > -1 && activeHIPTailStart > -1;
531-
activeHIPTailEnd(shouldCloseTail && activeHIPTailEnd < 0) = hipTb;
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;
532611

533612
// Closing tails will store them to global memory and zero the range
534613
// So it's enough to disable this part to fully disable the tail filter
@@ -556,6 +635,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
556635
.pad = uint16_t(pad),
557636
.tailStart = uint16_t(activeHIPTailStart[p]),
558637
.tailEnd = uint16_t(activeHIPTailEnd[p]),
638+
.satStart = int16_t(activeHIPTailSatStart[p]),
639+
.satEnd = int16_t(activeHIPTailSatEnd[p]),
559640
.qTot = tailQtot,
560641
.qMax = tailQMax,
561642
};
@@ -568,11 +649,15 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
568649

569650
activeHIPTailStart(hipTb > -1) = hipTb;
570651
activeHIPTailEnd(hipTb > -1) = -1;
652+
activeHIPTailSatStart(hipTb > -1) = newSatStart;
653+
activeHIPTailSatEnd(hipTb > -1) = newSatEnd;
571654
tailFilterCharge(hipTb > -1) = MaxADC;
572655

573656
aboveThresholdStartV[iVecPad] = aboveThresholdStart;
574657
activeHIPTailStartV[iVecPad] = activeHIPTailStart;
575658
activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
659+
activeHIPTailSatStartV[iVecPad] = activeHIPTailSatStart;
660+
activeHIPTailSatEndV[iVecPad] = activeHIPTailSatEnd;
576661
tailFilterChargeV[iVecPad] = tailFilterCharge;
577662

578663
} // for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
@@ -583,6 +668,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
583668

584669
auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
585670
auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
671+
const auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
672+
const auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
586673

587674
const auto shouldCloseTail = activeHIPTailStart > -1;
588675
activeHIPTailEnd(shouldCloseTail && activeHIPTailEnd < 0) = fragment.length;
@@ -611,6 +698,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
611698
.pad = uint16_t(pad),
612699
.tailStart = uint16_t(activeHIPTailStart[p]),
613700
.tailEnd = uint16_t(activeHIPTailEnd[p]),
701+
.satStart = int16_t(activeHIPTailSatStart[p]),
702+
.satEnd = int16_t(activeHIPTailSatEnd[p]),
614703
.qTot = tailQtot,
615704
.qMax = tailQMax,
616705
};
@@ -675,6 +764,10 @@ GPUd() void GPUTPCCFHIPTailConnector::Thread<0>(int32_t nBlocks, int32_t nThread
675764
return t1.tailStart < t2.tailStart;
676765
} else if (t1.tailEnd != t2.tailEnd) {
677766
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;
678771
} else if (t1.qTot != t2.qTot) {
679772
return t1.qTot < t2.qTot;
680773
} else {
@@ -747,20 +840,23 @@ GPUd() void GPUTPCCFHIPClusterizer::Thread<0>(int32_t nBlocks, int32_t nThreads,
747840
float qMax = 0;
748841
float padSum = 0;
749842
float padSqSum = 0;
750-
float timeSum = 0;
843+
float satTimeSum = 0;
844+
uint32_t nSatTails = 0;
751845
uint32_t tailStart = (uint32_t)-1;
752846
uint32_t tailEnd = 0;
753847

754848
// Zero-th element is empty tail
755849
for (; tail != tails; tail = &tails[tail->iNext]) {
756850
const float tailWeight = tail->qTot;
757851
const float tailPad = tail->pad;
758-
const float tailTime = HIPTailTimeMean(*tail);
759852
qMax = CAMath::Max(qMax, tail->qMax);
760853
qTot += tail->qTot;
761854
padSum += tailWeight * tailPad;
762855
padSqSum += tailWeight * tailPad * tailPad;
763-
timeSum += tailWeight * tailTime;
856+
if (tail->satStart >= 0 && tail->satEnd >= 0) {
857+
satTimeSum += 0.5f * (tail->satStart + tail->satEnd);
858+
nSatTails++;
859+
}
764860
tailStart = CAMath::Min<uint32_t>(tailStart, tail->tailStart);
765861
tailEnd = CAMath::Max<uint32_t>(tailEnd, tail->tailEnd);
766862

@@ -769,20 +865,24 @@ GPUd() void GPUTPCCFHIPClusterizer::Thread<0>(int32_t nBlocks, int32_t nThreads,
769865

770866
const float weightSum = CAMath::Max(qTot, 1.f);
771867
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.
773868
const float padSigma = CAMath::Sqrt(CAMath::Max(0.f, padSqSum / weightSum - padMean * padMean));
774869

775870
tpc::ClusterNative cn;
776871
cn.qMax = qMax;
777872
cn.setSaturatedQtot(qTot);
778873
cn.setSaturatedTailLength(tailEnd - tailStart);
779-
float clusterTime = fragment.start + timeMean - clusterer.Param().rec.tpc.clustersShiftTimebinsClusterizer;
780-
cn.setTimeFlags(clusterTime, 0);
781874
cn.setPad(padMean);
782875
cn.setSigmaPad(padSigma);
783876

784877
if (cn.qMax >= 1023) {
785878

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 = fragment.start + satTimeSum / nSatTails - clusterer.Param().rec.tpc.clustersShiftTimebinsClusterizer;
883+
assert(!CAMath::IsNaN(clusterTime));
884+
cn.setTimeFlags(clusterTime, 0);
885+
786886
uint32_t index;
787887

788888
if (!onlyMC) {

‎GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct HIPTailDescriptor {
4040
uint16_t pad;
4141
uint16_t tailStart;
4242
uint16_t tailEnd;
43+
int16_t satStart; // First timebin of the saturated plateau that triggered the tail on this pad, -1 if the tail was only inherited from a neighboring pad
44+
int16_t satEnd; // Last timebin of that plateau, -1 if inherited
4345
float qTot;
4446
float qMax;
4547
};
@@ -113,6 +115,12 @@ class GPUTPCCFCheckPadBaseline : public GPUKernelTemplate
113115
int16_t aboveThresholdStart = -1; // first TB of current above-hipTailThreshold streak; used to extend the tail back over the rising edge before saturation
114116
HipTailRange activeHIPTail{-1, -1};
115117
tpccf::Charge tailFilterCharge = 0;
118+
int16_t plateauStart = -1; // first TB of the current / last saturated plateau, only tracked in chunks with a HIP trigger
119+
bool plateauOpen = false; // previous TB was saturated
120+
int16_t satStart = -1; // saturated plateau that triggered in the current chunk
121+
int16_t satEnd = -1;
122+
int16_t activeSatStart = -1; // saturated plateau of the active tail, -1 if inherited from a neighbor
123+
int16_t activeSatEnd = -1;
116124
};
117125

118126
typedef GPUTPCClusterFinder processorType;

0 commit comments

Comments
 (0)