|
22 | 22 | #ifndef GPUCA_GPUCODE |
23 | 23 | #include "MCLabelAccumulator.h" |
24 | 24 | #include "utils/VcShim.h" |
| 25 | +#include <vector> |
25 | 26 | #endif |
26 | 27 |
|
27 | 28 | #if 0 |
@@ -344,7 +345,7 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t |
344 | 345 |
|
345 | 346 | GPUbarrier(); |
346 | 347 |
|
347 | | - } // if (hipTriggerFound) |
| 348 | + } // if (hasHIPTrigger) |
348 | 349 |
|
349 | 350 | } // for (uint16_t t = firstTB; t < lastTB; t += NumOfCachedTBs) |
350 | 351 |
|
@@ -374,60 +375,264 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t |
374 | 375 | GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer) |
375 | 376 | { |
376 | 377 | #ifndef GPUCA_GPUCODE |
377 | | - const CfFragment& fragment = clusterer.mPmemory->fragment; |
378 | | - CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap)); |
379 | | - |
380 | | - CfChargePos basePos(iBlock * PadsPerCacheline, 0); |
381 | | - |
382 | | - constexpr GPUTPCGeometry geo; |
383 | | - if (basePos.pad() >= geo.NPads(basePos.row())) { |
| 378 | + if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) { |
384 | 379 | return; |
385 | 380 | } |
386 | 381 |
|
387 | | - constexpr size_t ElemsInTileRow = (size_t)TilingLayout<GridSize<2>>::WidthInTiles * TimebinsPerCacheline * PadsPerCacheline; |
| 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)); |
388 | 395 |
|
389 | 396 | using UShort8 = Vc::fixed_size_simd<uint16_t, PadsPerCacheline>; |
| 397 | + using Short8 = Vc::fixed_size_simd<int16_t, PadsPerCacheline>; |
390 | 398 | using Charge8 = Vc::fixed_size_simd<float, PadsPerCacheline>; |
391 | 399 |
|
392 | | - UShort8 totalCharges{Vc::Zero}; |
393 | | - UShort8 consecCharges{Vc::Zero}; |
394 | | - UShort8 maxConsecCharges{Vc::Zero}; |
395 | | - Charge8 maxCharge{Vc::Zero}; |
396 | | - |
397 | | - tpccf::TPCFragmentTime t = fragment.firstNonOverlapTimeBin(); |
398 | | - |
399 | | - // Access packed charges as raw integers. We throw away the PackedCharge type here to simplify vectorization. |
400 | | - const uint16_t* packedChargeStart = reinterpret_cast<uint16_t*>(&chargeMap[basePos.delta({0, t})]); |
401 | | - |
402 | | - for (; t < fragment.lastNonOverlapTimeBin(); t += TimebinsPerCacheline) { |
403 | | - for (tpccf::TPCFragmentTime localtime = 0; localtime < TimebinsPerCacheline; localtime++) { |
404 | | - const UShort8 packedCharges{packedChargeStart + PadsPerCacheline * localtime, Vc::Aligned}; |
405 | | - const UShort8::mask_type isCharge = packedCharges != 0; |
406 | | - |
407 | | - if (isCharge.isNotEmpty()) { |
408 | | - totalCharges(isCharge)++; |
409 | | - consecCharges += 1; |
410 | | - consecCharges(not isCharge) = 0; |
411 | | - maxConsecCharges = Vc::max(consecCharges, maxConsecCharges); |
412 | | - |
413 | | - // Manually unpack charges to float. |
414 | | - // Duplicated from PackedCharge::unpack to generate vectorized code: |
415 | | - // Charge unpack() const { return Charge(mVal & ChargeMask) / Charge(1 << DecimalBits); } |
416 | | - // Note that PackedCharge has to cut off the highest 2 bits via ChargeMask as they are used for flags by the cluster finder |
417 | | - // and are not part of the charge value. We can skip this step because the cluster finder hasn't run yet |
418 | | - // and thus these bits are guarenteed to be zero. |
419 | | - const Charge8 unpackedCharges = Charge8(packedCharges) / Charge(1 << PackedCharge::DecimalBits); |
420 | | - maxCharge = Vc::max(maxCharge, unpackedCharges); |
421 | | - } else { |
422 | | - consecCharges = 0; |
423 | | - } |
| 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 | + |
| 431 | + const CfChargePos basePos(row, iVecPad * PadsPerCacheline, t); |
| 432 | + |
| 433 | + for (tpccf::TPCFragmentTime localtime = 0; localtime < NumOfCachedTBs; localtime++) { |
| 434 | + |
| 435 | + const uint16_t* packedChargeStart = reinterpret_cast<uint16_t*>(&chargeMap[basePos.delta({0, localtime})]); |
| 436 | + const UShort8 packedCharges = t + localtime < fragment.length |
| 437 | + ? UShort8{packedChargeStart, Vc::Aligned} : 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; |
424 | 489 | } |
425 | 490 |
|
426 | | - packedChargeStart += ElemsInTileRow; |
427 | | - } |
| 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; |
| 552 | + if (tailIdx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) { |
| 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; |
| 607 | + if (tailIdx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) { |
| 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 | + } |
428 | 619 |
|
429 | | - for (tpccf::Pad localpad = 0; localpad < PadsPerCacheline; localpad++) { |
430 | | - updatePadBaseline(basePos.gpad + localpad, clusterer, totalCharges[localpad], maxConsecCharges[localpad], maxCharge[localpad]); |
| 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 | + } |
431 | 636 | } |
432 | 637 | #endif |
433 | 638 | } |
@@ -463,16 +668,23 @@ GPUd() void GPUTPCCFHIPTailConnector::Thread<0>(int32_t nBlocks, int32_t nThread |
463 | 668 | #ifdef GPUCA_DETERMINISTIC_MODE |
464 | 669 | // Races in tail comparisons and atomic swap can lead to slightly different clusters. |
465 | 670 | // So need a sequential fallback for deterministic mode |
466 | | - if (iThread > 0) { |
467 | | - return; |
468 | | - } |
469 | | - nThreads = 1; |
470 | 671 | GPUCommonAlgorithm::sortInBlock(tails + 1, tails + nTails + 1, [](auto&& t1, auto&& t2) { |
471 | 672 | if (t1.pad != t2.pad) { |
472 | 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; |
473 | 682 | } |
474 | | - return t1.tailStart < t2.tailStart; |
475 | 683 | }); |
| 684 | + if (iThread > 0) { |
| 685 | + return; |
| 686 | + } |
| 687 | + nThreads = 1; |
476 | 688 | #endif |
477 | 689 |
|
478 | 690 | for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads) { |
|
0 commit comments